在折腾stl的时候遇到std::ref和std::reference_wrapper这两个概念,没有搜到什么简明的资料,所以自己来琢磨一下。
综述
首先引用《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()的一个实际用处。(以下内容部分翻译自这篇国外的博客 /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);
reference_wrapper对象
因为ref()返回的是一个reference_wrapper对象,并不是该对象的引用,所以如果我们要对返回对象调用成员函数就会报错。仍以functest为例,这次我们在functest2里调用成员函数。
template<typename T>
void functest2(T a){
();
}
class objTest{
private:
int number;
public:
objTest(int n=0):number(n){
}
friend ostream& operator<<(ostream& o,const objTest& obj){
o<< ;
return o;
}
void incre(){
++number;
}
};
int main(){
objTest(1);
functest2(objTest); //error
}
这时候就需要使用reference wrapper对象的get()方法,返回真正的引用(实际上reference wrapper是用指针表现出引用的所有特性,所以返回的应该是指针指向的对象)。
template<typename T>
void functest2(T a){
().incre();
}
也许你会疑问,在functest()中++a为什么不需要get()?那是因为reference_wrapper支持隐式转换。在其类模板有用户定义转换:
operator T& () const noexcept;
支持将reference wrapper对象转换为引用,且没有声明为explicit,所以支持隐式转换。
reference_wrapper的一个用例
reference wrapper的一大用处就是,stl容器提供的是value语义而不是reference语义,所以容器不支持元素为引用,而用reference_wrapper可以实现。以下代码摘自/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((), (), -4);
std::vector<std::reference_wrapper<int>> v((), ());
// can't use shuffle on a list (requires random access), but can use it on a vector
std::shuffle((), (), 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元素也出现相应变化。