首先引用《C++标准库(第二版)》5.4.3节对此的介绍
声明于 <functional> 中的 class std::reference_wrapper<> 主要用来“喂 ” reference 给function template, 后者原本以 by value方式接受参数。对于一个给定类型 T ,这个 class 提供 ref () 用以隐式转换为 T& ,一个 cref () 用以隐式转换为 const T& ,这往往允许 function template 得以操作 reference 而不需要另写特化版本。
简单来说,就是让按值传参的模板可以接受一个引用作为参数。
我们可以写个functest检测一下。
#include<iostream> #include<functional> using namespace std; template<typename T> void functest(T a){ ++a; } int main(){ int a=1; int& b=a; functest(a); cout<< a<<endl; //1 functest(b); cout<< a<<endl; //1 functest(ref(a)); cout<< a<<endl; //2 }b是a的引用,调用functest(a) functest(b),a并没有自增。因为模板参数是一个value而不是reference(值传递而非引用),自增的是一个临时对象,而使用ref(),就可以让模板接受一个reference作为参数,所以a的值发生改变。
需要注意的是,ref()是利用模板参数推导实现的,如果你创建一个按值传参的非模板函数而想传递一个引用,ref()是做不到的。
下面来看ref()的实际用处。(以下内容部分翻译自这篇国外的博客 https://oopscenities.net/2012/08/09/reference_wrapper/)
#include <iostream> #include <functional> using namespace std; using namespace std::placeholders; void add(int a, int b, int& r) { r = a + b; } int main() { int result = 0; auto f = bind(add, _1, 20, result); f(80); cout << result << endl; //0 return 0; }bind()是一个函数模板,简单来说它可以根据一个已有的函数,生成另一个函数,但是由于bind()不知道生成的函数执行的时候传递的参数是否还有效,所以它选择按值传参而不是按引用传参。这样对于参数为引用的函数(比如上面代码中的add()),使用bind()就会达不到预期效果。解决的办法就是给需要使用引用的参数包裹一层reference_wrapper。
int main() { int result = 0; auto f = bind(add, _1, 20, ref(result)); f(80); cout << result << endl; //100 return 0; }ref()返回一个reference_wrapper对象,事实上,ref()就是用reference wrapper来包裹对象的一个简化写法。
auto r=ref(o); //等价于 referencce_wrapper<dectype(o)> r(o);同理在多线程函数中:
即使threadCallback接受参数作为引用,但仍然进行了更改,在线程外部也不可见。这是因为线程函数threadCallback中的x引用了在新线程的堆栈上复制的临时值。
#include <iostream> #include <thread> void threadCallback(int const & x) { int & y = const_cast<int &>(x); y++; std::cout<<"Inside Thread x = "<<x<<std::endl; } int main() { int x = 9; std::cout<<"In Main Thread : Before Thread Start x = "<<x<<std::endl; std::thread threadObj(threadCallback,std::ref(x)); threadObj.join(); std::cout<<"In Main Thread : After Thread Joins x = "<<x<<std::endl; return 0; }因为ref()返回的是一个reference_wrapper对象,并不是该对象的引用,所以如果我们要对返回对象调用成员函数就会报错。仍以functest为例,这次我们在functest2里调用成员函数。
template<typename T> void functest2(T a){ a.incre(); } class objTest{ private: int number; public: objTest(int n=0):number(n){ } friend ostream& operator<<(ostream& o,const objTest& obj){ o<< obj.number; return o; } void incre(){ ++number; } }; int main(){ objTest(1); functest2(objTest); //error }这时候就需要使用reference wrapper对象的get()方法,返回真正的引用(实际上reference wrapper是用指针表现出引用的所有特性,所以返回的应该是指针指向的对象)。
template<typename T> void functest2(T a){ a.get().incre(); }也许你会疑问,在functest()中++a为什么不需要get()?那是因为reference_wrapper支持隐式转换。在其类模板有用户定义转换:
operator T& () const noexcept;
支持将reference wrapper对象转换为引用,且没有声明为explicit,所以支持隐式转换。
reference wrapper的一大用处就是,stl容器提供的是value语义而不是reference语义,所以容器不支持元素为引用,而用reference_wrapper可以实现。以下代码摘自http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
#include <algorithm> #include <list> #include <vector> #include <iostream> #include <numeric> #include <random> #include <functional> int main() { std::list<int> l(10); std::iota(l.begin(), l.end(), -4); std::vector<std::reference_wrapper<int>> v(l.begin(), l.end()); // can't use shuffle on a list (requires random access), but can use it on a vector std::shuffle(v.begin(), v.end(), std::mt19937{std::random_device{}()}); std::cout << "Contents of the list: "; for (int n : l) std::cout << n << ' '; std::cout << '\n'; std::cout << "Contents of the list, as seen through a shuffled vector: "; for (int i : v) std::cout << i << ' '; std::cout << '\n'; std::cout << "Doubling the values in the initial list...\n"; for (int& i : l) { i *= 2; } std::cout << "Contents of the list, as seen through a shuffled vector: "; for (int i : v) std::cout << i << ' '; std::cout << '\n'; }输出为:
Contents of the list: -4 -3 -2 -1 0 1 2 3 4 5 Contents of the list, as seen through a shuffled vector: -1 2 -2 1 5 0 3 -3 -4 4 Doubling the values in the initial list… Contents of the list, as seen through a shuffled vector: -2 4 -4 2 10 0 6 -6 -8 8
可以看到vector的元素都是list中元素的引用,list做了随机重排后,vector元素也出现相应变化。