std::ref
是什么?关于c++中的std::ref,std::ref在c++11引入。本文通过讲解std::ref的常用方式,及剖析下std::ref内部实现,然后我们再进一步分析为什么使用std::ref。
ref
是个函数模板:
reference_wrapper
对象并返回,该对象拥有传入的elem
变量的引用。如果参数本身是一个reference_wrapper
类型的对象,则创建该对象的一个副本,并返回。std::ref
std::ref主要在函数式编程(如std::bind)时使用,bind是对参数直接拷贝,无法传入引用(即使你传入的实参是引用类型也不行),故引入std::ref()。使用std::ref可以在模板传参的时候传入引用。
std::ref能使用reference_wrapper包装好的引用对象代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
代码如下:
#include
#include
#include
using namespace std;
//std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用
void f(int& a, int& b, int& c)
{
cout << "in function a = " << a << " b = " << b << " c = " << c << endl;
cout << "in function a = " << &a << " b = " << &b << " c = " << &c << endl;
a += 1;
b += 10;
c += 100;
}
int main() {
int n1 = 1, n2 = 10, n3 = 100;
int& r1 = n1;
int& r2 = n2;
function f1 = bind(f, r1, r2, ref(n3));
//前两个参数即便是引用类型,bind 传入的还是其值的拷贝,第三个参数传入 reference_wrapper 对象,该对象可隐式的转换为值的引用
f1();
cout << "out function a = " << n1 << " b = " << n2 << " c = " << n3 << endl;
cout << "out function a = " << &n1 << " b = " << &n2 << " c = " << &n3 << endl;
f1();
cout << "out function a = " << n1 << " b = " << n2 << " c = " << n3 << endl;
cout << "out function a = " << &n1 << " b = " << &n2 << " c = " << &n3 << endl;
return 0;
}
输出结果:
in function a = 1 b = 10 c = 100
in function a = 0000006B90EFF710 b = 0000006B90EFF708 c = 0000006B90EFF684
out function a = 1 b = 10 c = 200
out function a = 0000006B90EFF644 b = 0000006B90EFF664 c = 0000006B90EFF684
in function a = 2 b = 20 c = 200
in function a = 0000006B90EFF710 b = 0000006B90EFF708 c = 0000006B90EFF684
out function a = 1 b = 10 c = 300
out function a = 0000006B90EFF644 b = 0000006B90EFF664 c = 0000006B90EFF684
std::ref
和引用的区别首先就是,上面的例子里,使用bind的时候,普通引用和std::ref引用有区别。
std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型或类型隐式转换时,std::ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
目前我只遇到过类型转换时,ref和普通引用的区别,模板自动推导类型的情况还没遇到过。
std::ref
用法int n1 = 0;
auto n2 = std::ref(n1);
n2++;
n1++;
std::cout << n1 << std::endl; // 2
std::cout << n2 << std::endl; // 2
可以看到 是把n1的引用传递给了n2,分别进行加法,可以看到n2是n1的引用,最终得到的值都是2。
那么大家可能会想,我都已经有了’int& a = b’的这种引用赋值的语法了,为什么c++11又出现了一个std::ref,我们继续来看例子:
#include
#include
void thread_func(int& n2) { // error, >> int n2
n2++;
}
int main() {
int n1 = 0;
std::thread t1(thread_func, n1);
t1.join();
std::cout << n1 << std::endl;
}
我们如果写成这样是编译不过的,除非是去掉引用符号,那么我如果非要传引用怎么办呢?
// snap ...
int main() {
int n1 = 0;
std::thread t1(thread_func, std::ref(n1));
t1.join();
std::cout << n1 << std::endl; // 1
}
这样可以看到引用传递成功,并且能够达到我们效果,我们再来看个例子:
#include
#include
void func(int& n2) {
n2++;
}
int main() {
int n1 = 0;
auto bind_fn = std::bind(&func, std::ref(n1));
bind_fn();
std::cout << n1 << std::endl; // 1
}
这里我们也发现std::bind这样也是需要通过std::ref来实现bind引用。
那么我们其实可以看的出来,std::bind或者std::thread里是做了什么导致我们原来的通过&传递引用的方式失效,或者说std::ref是做了什么才能使得我们使用std::bind和std::thread能够传递引用。
那么我们展开std::ref看看他的真面目,大致内容如下:
template
reference_wrapper<_Ty> ref(_Ty& _Val) noexcept {
return reference_wrapper<_Ty>(_Val);
}
std::ref
template
_NODISCARD _CONSTEXPR20 _Binder<_Unforced, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args) {
return _Binder<_Unforced, _Fx, _Types...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}
看到是构造了一个_Binder的对象返回,bound参数作为构造函数的参数传入。
using _Second = tuple...>; //std::decay_t会移除掉引用属性
_Compressed_pair<_First, _Second> _Mypair;
constexpr explicit _Binder(_Fx&& _Func, _Types&&... _Args)
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...) {}
也可以看到构造函数中,参数传递给_Mypair成员。到这里结束。
我们再看下调用时:
#define _CALL_BINDER \
_Call_binder(_Invoker_ret<_Ret>{}, _Seq{}, _Mypair._Get_first(), _Mypair._Myval2, \
_STD forward_as_tuple(_STD forward<_Unbound>(_Unbargs)...))
template
_CONSTEXPR20 auto operator()(_Unbound&&... _Unbargs) noexcept(noexcept(_CALL_BINDER)) -> decltype(_CALL_BINDER) {
return _CALL_BINDER;
}
std::ref
总结std::ref
的一些用法,然后我们讲解std::ref
是通过std::reference_wrapper实现,然后我们借助了cppreference上的实现来给大家剖析了他本质就是存放了对象的地址(类似指针的用法),还讲解了noexcept等语法,最后我们讲解了下std::bind为什么要使用到reference_wrapper。