C++程序员应了解的那些事(60)浅析std::ref与reference_wrapper

目录

综述 

【简单测试】

【std::ref()的一个用例~ 用于std::bind】

【reference_wrapper对象极其get()方法】

【reference_wrapper的一个用例:支持容器元素为引用】

【C++ ref和引用(&)// std::bind参数使用std::ref~示例】

综述 

        首先引用《C++标准库(第二版)》5.4.3节对此的介绍:

        声明于 <functional> 中的 class std::reference_wrapper<> 主要用来“喂 ” reference 给function template,         后者原本以 by value方式接受参数。对于一个给定类型 T ,这个 class 提供 ref () 用以隐式转换为 T& ,一个 cref () 用以隐式转换为 const T& ,这往往允许 function template 得以操作 reference 而不需要另写特化版本。

        简单来说,就是让按值传参的模板可以接受一个引用作为参数。

【简单测试】

#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
}
输出:
[Running] g++ main.cpp testModule/test_map.cpp -o main && "d:\C++study\"main
1
1
2

       b是a的引用,调用functest(a) functest(b),a并没有自增。因为模板参数是一个value而不是reference,自增的是一个临时对象,而使用ref(),就可以让模板接受一个reference作为参数,所以a的值发生改变。
     
 需要注意的是,ref()是利用模板参数推导实现的,如果你创建一个按值传参的非模板函数而想传递一个引用,ref()是做不到的。

【std::ref()的一个用例~ 用于std::bind】

#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 = std::bind(add, _1, 20, result); //_1表示占位符,位于<functional>中,std::placeholders::_1;
    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);

【reference_wrapper对象极其get()方法】

       因为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可以实现。

#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';
}
//代码摘自http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
输出:
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

【C++ ref和引用(&)// std::bind参数使用std::ref~示例】

        std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。

        std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用。其中代表的例子是thread,比如thread的方法传递引用的时候,必须外层用ref来进行引用传递,否则就是浅拷贝!!

class TestRef
{
public:
	TestRef():i(0)//默认构造函数 
	{
		cout << "TestRef默认构造函数" << this << endl;
	} 	
	TestRef(const TestRef&testR) :i(0) //拷贝构造函数
	{
		cout << "TestRef拷贝构造函数" << this << endl;
	}
	~TestRef()//析构函数
	{
		cout << "TestRef析构函数" << this << endl;
	}
	mutable int i ;
};
class TestRef1
{
public:
	TestRef1() :i(0)//默认构造函数 
	{
		cout << "TestRef1默认构造函数" << this << endl;
	}
	TestRef1(const TestRef1& testR) :i(0) //拷贝构造函数
	{
		cout << "TestRef1拷贝构造函数" << this << endl;
	}
	~TestRef1()//析构函数
	{
		cout << "TestRef1析构函数" << this << endl;
	}
	mutable int i=0;
};

void funs(const TestRef&test1, const TestRef1&test2)
{
	cout << "function " << "test1:" << &test1 <<" "<< test1.i << endl;
	cout << "function " << "test2:" << &test2 <<" "<< test2.i << endl;
	test1.i++;
	test2.i++;
}
void testFunction()
{
	TestRef t1;
	TestRef1 t2;
	cout << "testFunction " << "t1:" << &t1 << " " << t1.i << " t2:" << " " << &t2 << " " << t2.i << endl;
	funs(t1,t2);
	cout << "testFunction " << "t1:" << &t1 <<" "<< t1.i << " t2:" <<" " << &t2 << " " << t2.i <<endl;
}
int main()
{
	testFunction();
}
输出:
[Running] g++ main.cpp testModule/test_map.cpp -o main && "d:\C++study\"main
TestRef默认构造函数0x22fd3c
TestRef1默认构造函数0x22fd38
testFunction t1:0x22fd3c 0 t2: 0x22fd38 0
function test1:0x22fd3c 0
function test2:0x22fd38 0
testFunction t1:0x22fd3c 1 t2: 0x22fd38 1
TestRef1析构函数0x22fd38
TestRef析构函数0x22fd3c

       直接用引用 &是可以改变i的值的,那么为什么还要用ref呢?修改testFunction()函数如下:

void testFunction()
{
	TestRef t1;
	TestRef1 t2;
	function<void()> f1 = bind(funs, t1, ref(t2));
	f1();
	cout << "testFunction " << "t1:" << &t1 << " " << t1.i << " t2:" << " " << &t2 << " " << t2.i << endl;
}

输出:
[Running] g++ main.cpp testModule/test_map.cpp -o main && "d:\C++study\"main
TestRef默认构造函数0x22fd1c
TestRef1默认构造函数0x22fd18
TestRef拷贝构造函数0x22fd30
TestRef拷贝构造函数0x2f7cd0
TestRef析构函数0x22fd30
function test1:0x2f7cd0 0
function test2:0x22fd18 0
testFunction t1:0x22fd1c 0 t2: 0x22fd18 1
TestRef析构函数0x2f7cd0
TestRef1析构函数0x22fd18
TestRef析构函数0x22fd1c

        通过输出的结果可以看到t1没有改变其中的i值,使用了ref的t2是直接把地址传给了test2,所以改变了其中i的值。
        但是通过结果可以看出来,test1是拷贝的对象,怎么拷贝了两个呢,而且有一个拷贝完就释放了,还有一个是主线程退出后才释放的,初步结论是使用 bind,第一个参数会拷贝一份,立即释放,第二个参数再拷贝一份,主线程退出后才释放。(个人感觉是第二个参数先拷贝,然后再拷贝给第一个参数(为待绑定的函数)的参数,然后析构先拷贝出来的那个对象)
        所以再调用一遍f1(),改变的肯定是拷贝出来的对象中i的值。

void testFunction()
{
	TestRef t1;
	TestRef1 t2;
    cout << "testFunction " << "t1:" << &t1 << " " << t1.i << " t2:" << " " << &t2 << " " << t2.i << endl;

	function<void()> f1 = bind(funs, t1, ref(t2));
	f1();
	cout << "testFunction " << "t1:" << &t1 << " " << t1.i << " t2:" << " " << &t2 << " " << t2.i << endl;
	f1();
	cout << "testFunction " << "t1:" << &t1 << " " << t1.i << " t2:" << " " << &t2 << " " << t2.i << endl;
}

在这里插入图片描述

        可以清晰的看到荧光绿是t1对象,而红色是构造出来的对象test1,多次调用f1(),其中test1都是第一次构造的那个对象,并且i累加了,但是在testFunction中t1中i的值依旧是0。t2和test2一直用的都是一个地址,也就是同一个对象,这也证实了上述的结论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
In file included from /home/yhdr/2-test-2023-06_v3/sent.h:24:0, from /home/yhdr/2-test-2023-06_v3/sent.cpp:1: /usr/include/c++/7/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >’: /usr/include/c++/7/thread:127:22: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(double*, double&, double&, double&, double&, double&); _Args = {double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>}]’ /home/yhdr/2-test-2023-06_v3/sent.cpp:18:153: required from here /usr/include/c++/7/thread:240:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >::_Indices)’ operator()() ^~~~~~~~ /usr/include/c++/7/thread:231:4: note: candidate: template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> >] _M_invoke(_Index_tuple<_Ind...>)
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值