std::ref与reference_wrapper

转载自: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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值