std::ref与reference

    xiaoxiao2022-07-05  180

    转载自:fiverwyp

    综述

    首先引用《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()是做不到的。

    std::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; }

     

    reference_wrapper对象

           因为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的一个用例

    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元素也出现相应变化。

    最新回复(0)