C++11中std::ref()与&
引言
最近看到一个多线程代码如下:
typedef unsigned long long ULL;
void accumulator_function(const std::vector<int> &v, ULL &acm,
unsigned int beginIndex, unsigned int endIndex)
{
acm = 0;
for (unsigned int i = beginIndex; i < endIndex; ++i)
{
acm += v[i];
}
}
int main()
{
ULL acm1 = 0;
ULL acm2 = 0;
std::vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };
std::thread t1(accumulator_function, std::ref(v),
std::ref(acm1), 0, v.size() / 2);
std::thread t2(accumulator_function2, std::ref(v),
std::ref(acm2), v.size() / 2, v.size());
t1.join();
t2.join();
std::cout << acm1 << "+" << acm2 << "=" << acm1 + acm2 << std::endl;
return 0;
}
其中创建线程的部分使用了std::thread t1(accumulator_function2, std::ref(v), std::ref(acm1), 0, v.size() / 2);
,对应的函数实现为void accumulator_function(const std::vector<int> &v, ULL &acm, unsigned int beginIndex, unsigned int endIndex)
,其中的std::ref()
之前在C++ Primer中看过,感觉应该和&
差不多吧,但是既然这样为什么仍然需要用ref转换成引用形式呢?立刻把std::ref()
全部删了,重新运行,结果报错了......有点意思,可以探究一波。
探究过程
引用的例子首先列举一个:
void fun(vector<int>& a, int i) {
a[i] = 20;
}
int main() {
std::vector<int> v = {1, 1, 1, 1, 1, 1};
for (auto x : v)
cout << x << ' ';
cout << endl;
fun(v, 3); // 等同于fun(std::ref(v), 3);
for (auto x : v)
cout << x << ' ';
}
// Output:
// 1 1 1 1 1 1
// 1 1 1 20 1 1
如果是普通引用的话,只需要调用fun(v, 3)
就行了,为什么在例子中使用了fun(std::ref(v),..)
这种形式呢?说明std::ref()
和&
是不一样的么?写个例子看一下:
#include <iostream>
#include <type_traits>
int main() {
int x = 5;
std::cout << std::boolalpha << std::is_same<int&, decltype(std::ref(x))>::value;
return 0;
}
输出答案果然是false!那么如果std::ref()
返回的不是对象的引用,返回的是什么?查一下手册可以发现:函数模板 ref
与 cref
是生成std::reference_wrapper
类型对象的辅助函数,它们用模板实参推导确定结果的模板实参。所以std::ref()
返回的实际上是一个reference_wrapper
而不是T&
,可以从一个指向不能拷贝的类型的对象的引用生成一个可拷贝的对象。std::reference_wrapper
是包装引用于可复制、可赋值对象的类模板。它常用作将引用存储入无法正常保有引用的标准容器(类似 std::vector )的机制。
修改一下上面的例子,看看结果:
#include <iostream>
#include <type_traits>
int main() {
int x = 5;
std::cout << std::boolalpha << std::is_same<int&, decltype(std::ref(x).get())>::value;
return 0;
}
变为true了。reference_wrapper
与&
并不一样,但是利用std::reference_wrapper
对象的get()
方法返回其引用,发现就是&
类型。但是为什么在多线程那个例子中要使用std::ref()
呢?
原因是C++的函数式编程(如std::bind
)在使用时是对参数直接拷贝,而不是引用。具体可以参照这一句话:std::reference_wrapper
亦用于按引用传递对象给 std::bind 或 std::thread 的构造函数。
还是通过代码理解一下:
#include <functional>
#include <iostream>
void f(int& n1, int& n2, const int& n3) {
std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
++n1; // increments the copy of n1 stored in the function object
++n2; // increments the main()'s n2
// ++n3; // compile errors
}
int main() {
int n1 = 1, n2 = 2, n3 = 3;
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
n1 = 4;
n2 = 5;
n3 = 6;
std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
bound_f();
std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}
/*Output:
Before function: 4 5 6
In function: 1 5 6
After function: 4 6 6
*/
上述代码在执行std::bind
后,在函数f()
中n1的值仍然是1,n2和n3改成了修改的值。说明std::bind
使用的是参数的拷贝而不是引用。C++11的设计者认为std::bind
默认应该采用拷贝,如果使用者有需求,加上std::ref()
即可。同理std::thread
也是这样。
结论
std::ref
只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref
根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型std::reference_wrapper
来代替原本会被识别的值类型,而std::reference_wrapper
能隐式转换为被引用的值的引用类型,但是其本身并不能被用作&
类型,可以通过其get()
方法获得其内部对象的引用。
而回到刚开始的那个多线程代码,std::thread
的方法传递引用的时候,我们希望使用的是参数的引用,而不是浅拷贝,所以必须用ref来进行引用传递。