目录
【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一直用的都是一个地址,也就是同一个对象,这也证实了上述的结论。