boost:ref 曾经终败给现在 2022-10-13 12:56 66阅读 0赞 > * ref类可以包装对象的引用,在传递参数时消除对象拷贝的代价,或者将不可拷贝的对象变为可以拷贝 > * 谓词参数:没有函数名,定义了动作,参数。 C++标准库和Boost中的算法大量使用了函数对象作为判断式或谓词参数,而这些参数都是传值语义、算法或函数在内部保留函数对象的拷贝并使用。比如: #include <iostream> #include <vector> #include <algorithm> struct square{ // 函数对象,计算整数的平方 typedef void result_type; //返回结果的类型定义 void operator()(int &x){ x = x * x; printf("%d\t", x); } }; int main (int argc, const char* argv[]) { std::vector<int> v = { 1, 2, 3, 4}; std::for_each(v.begin(), v.end(), square()); printf("\n"); std::for_each(v.begin(), v.end(), [](int i){ printf("%d\t", i);}); return 0; } ![在这里插入图片描述][20210707134344677.png] 一般情况下传值语义是可行的,但也有很多特殊情况:作为参数的函数对象拷贝代价太高(具有复杂的内部状态),不希望拷贝对象(内部状态不应该被改变),甚至拷贝是不可行的(noncopyable、singleton)。 boost.ref应用代理模式,引入对象引用的包装器概念解决了这个问题。它位于名字空间boost,需要包含头文件< boost/ref.hpp>: #include <boost/ref.hpp> using namespace boost; # 类摘要 # ref库定义了一个很小很简单的引用类型的包装器,名字叫做reference\_wrapper,类摘要如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70] reference\_wrapper的构造函数接受类型T的引用类型,内部使用指针存储指向t的引用,构造出一个reference\_wrapper对象,从而包装了引用get()和get\_pointer()这两个函数分别返回存储的引用和指针,相当于解开对t的包装。 请注意:reference\_wrapper的构造函数被声明为explicit,因此必须在创建reference\_wrapper对象时就赋值初始化,就像是使用一个引用类型的变量。 reference\_wrapper还支持隐式类型转换,可以在需要的语境下返回存储的引用,因此它很像引用类型,能够在任何需要T出现的地方使用reference\_wrapper。 # 基本用法 # reference\_wrapper的用法有些类似C++中的引用类型(T&),就像是被包装对象的一个别名。但它只有在使用T的语境下才能执行隐式转换,其他的情况下则需要调用类型 int x = 10; reference_wrapper<int> rw(x); //包装为int类型的引用 assert(x == rw); //隐式转换为int类型 (int &)rw = 100; //显式转换为int&类型 assert(x == 100); reference_wrapper<int> rw2(rw); //拷贝构造 assert(rw2.get() == 100); std::string str; reference_wrapper<std::string> rws(str); //包装字符串的引用 *rws.get_pointer() = "test reference_wrapper";//指针操作 printf("%lu", rws.get().size()); // 注意上面的最后一行,因为不存在一个要求string类型的隐式转换,所以我们必须要使用get()获得被包装的真正对象,然后调用它的方法。如果写出如下的语句: printf("%lu", rws.size()); 将引发一个编译错误:reference\_wrapper不存在size()成员函数。 因此,reference\_wrapper是一个很像引用的对象,但它与真正的引用无论在用法还是在用途上都存在差异。 # 工厂函数 # reference\_wrapper的名字过长,声明包装对象很不方便,因而ref提供了两个便捷的工厂函数ref()和cref(),可以通过参数类型推导很容易地构造reference\_wrapper对象。 这两个函数的声明如下: ![在这里插入图片描述][20210707140712902.png] ref()和cref()会根据参数类型自动的推导生成正确的reference\_wrapper< T >对象,ref()产生的类型是T,而cref()产生的类型是T const。比如: double x = 2.71463; auto rw = cref(x); std::cout << typeid(rw).name() << "\n"; std::string str; auto rws = boost::ref(x); //使用名字空间限定,避免ADL std::cout << typeid(rws).name() << "\n"; 因为reference\_wrapper支持拷贝,因此ref()和cref()可以直接用在需要拷贝语义的函数参数中,而不必专门使用一个reference\_wrapper来暂存,比如: double x = 2.71463; std::cout << std::sqrt(ref(x)) << "\n"; # 操作包装 # ref库运用模板元编程技术提供两个特征类is\_reference\_wrapper和unwrap\_reference,用于检测reference\_wrapper对象: * is\_reference\_wrapper< T >::value可以判断T是否被包装 * unwrap\_reference< T >::type表明了T的真实类型,无论它是否经过包装 示范这两个特性类用法的代码如下: std::vector<int> v(10, 2); auto rw = boost::cref(v); assert(is_reference_wrapper<decltype(rw)>::value); assert(!is_reference_wrapper<decltype(v)>::value); std::string str; auto rws = boost::ref(str); std::cout << typeid(unwrap_reference<decltype(rw)>::type).name() << "\n"; std::cout << typeid(unwrap_reference<decltype(rws)>::type).name() << "\n"; 自由函数unwrap\_ref()为解开包装提供了简单的方法,它利用unwrap\_reference< T >直接解开reference\_wrapper的包装(如果有的话),返回被包装对象的引用。比如: std::set<int> s; auto rw = boost::ref(s); //获得一个包装对象 unwrap_ref(rw).insert(12); //直接解开包装 std::string str("test"); auto rws = boost::cref(str); //获得一个常对象的包装 std::cout << unwrap_ref(rws) << std::endl; //解包装 直接对一个未包装的对象使用unwrap\_ref()也是允许的,它将直接返回对象自身的引用: std::cout << unwrap_ref(str) << std::endl; //对未包装对象解包装 unwrap\_ref()的这个功能很有用,可以把unwrap\_ref()安全的用在泛型代码中,从而不必关心对象的包装特性,总能够正确的操作对象 # 综合应用 # 假设我们有一个很大的类big\_class,它具有复杂的内部状态,构造、拷贝都具有很高的代价: class big_class{ //一个简化的例子 private: int x; //很复杂的内部状态,这里从简 public: big_class() : x(0){ } //构造函数 void print(){ //一个操作函数,改变内部状态 printf("big class %d", ++x); } }; 模板函数print()接受任意类型的参数,调用它们的成员函数printf()。考虑到类型可能会使用referen\_wrapper保证,它使用unwrap\_ref()函数: template<typename T> void print(T a){ for(int i = 0; i < 2; ++i){ unwrap_ref(a).print(); } } 最后使用: big_class c; auto rw = ref(c); c.print(); // 输出 1 print(c); // 拷贝传参,输出2,3,内部状态不改变 print(rw); // 引用传参,输出2,3,内部状态改变 c.print(); // 输出4 这里演示了拷贝传参和引用传参的不同。当调用print©时是拷贝传参,因此c在函数中被复制,它内部状态的编号不影响原对象,在函数调用完之后c的内部值仍然是1。但调用print(rw)时由于使用了reference\_wrapper保证,函数拷贝的是reference\_wrapper对象,在函数内部被解包装为原对象的引用,因此改变了原对象的内部状态。 # 对比标准 # ref将对象包装为引用语义,降低了复制的代价,使引用的行为更像对象(因为对象更有用更强大),可以让容器安全的持有被包装的引用对象,可以被称为是智能引用。因此它被收入了C++标准 C++标准里的std::reference\_wrapper摘要如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1] std::reference\_wrapper是由boost.ref库演变而来的,所以两者非常相似,但也有少量的不同,最大的区别在于std::reference\_wrapper支持调用操作符------这使得我们可以保证一个函数对象的引用并传递给标准库算法: #include <iostream> #include <vector> #include <algorithm> struct square{ // 函数对象,计算整数的平方 typedef void result_type; //返回结果的类型定义 void operator()(int &x){ x = x * x; } }; int main (int argc, const char* argv[]) { typedef double (*pfunc)(double ); pfunc pf = sqrt; std::cout << std::ref(pf)(5.0) << "\n"; //包装函数指针 square sq; int x = 5; std::ref(sq)(x); //包装函数对象 std::cout << x << "\n"; std::vector<int> v = { 1, 2, 3, 4, 5}; std::for_each(v.begin(), v.end(), std::ref(sq)); std::for_each(v.begin(), v.end(), [](int i){ printf("%d\t", i);}); return 0; } [20210707134344677.png]: /images/20221005/982c514213af405293ebca1c1e274779.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]: /images/20221005/00d81a56d6864e3e87f4a1593897fb7e.png [20210707140712902.png]: /images/20221005/8103b109fa4b44e49b3d8cc14516e895.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1]: /images/20221005/62969313b3de475ca64acf0a0a958aaa.png
还没有评论,来说两句吧...