完美转发
引用折叠
也就是在c++11
里面是没有引用的引用的T& &r=x
这时候会发生引用折叠
也就是一般发生在
- 模板实例化
auto
decltype
typedef
的语境中
引用折叠的规则
- 所有的右值引用折叠到右值引用上还是一个右值引用
- 所有的其他引用的折叠都会变成左值引用,也就是说,左值引用具有传染性,粘上一个左值引用那就是左值引用了
所以有个概念叫做万能引用
(1)当万能引用(T&& param)
绑定到左值时,由于万能引用也是一个引用,而左值只能绑定到左值引用。因此,T会被推导为T&类型。从而param
的类型为T& &&,引用折叠后的类型为T&。
(2)当万能引用(T&& param)
绑定到右值时,同理,右值只能绑定到右值引用上,故T会被推导为T类型。从而param
的类型就是T&&(右值引用)。
- 当传递给func函数的实参类型为左值Widget时,T被推导为Widget&类别。然后forward会实例化为std::forward<Widget&>,并返回Widget&(左值引用,根据定义是个左值!)
- 而当传递给func函数的实参类型为右值Widget时,T被推导为Widget。然后forward被实例化为std::forward<Widget>,并返回Widget&&(注意,匿名的右值引用是个右值!)
- 可见,std::forward会根据传递给func函数实参(注意,不是形参)的左/右值类型进行转发。当传给func函数左值实参时,forward返回左值引用,并将该左值转发给process。而当传入func的实参为右值时,forward返回右值引用,并将该右值转发给process函数。
主要跟传参有关
string a("acb");
string&& b = std::move(a);
string c(b); // 这里其实调用的是拷贝构造
string d(std::forward<string>(b)); // 这里调用的才是移动构造
右值引用是独立于值的,一个右值引用作为函数的形参,在函数的内部转发该参数的时候就已经变成一个左值并不是原来的类型
因为不论 左值引用 还是 右值引用 的变量(或参数)在初始化后,都是左值(参考 [sec|值类别 vs 变量类型]):
命名的右值引用(named rvalue reference)变量 是 左值,但变量类型 却是 右值引用
在作用域内,左值变量 可以通过 变量名(variable name)被取地址、被赋值
所以,返回右值引用变量时,需要使用 std::move()/std::forward() 显式的 [sec|移动转发] 移动转发 或 [sec|完美转发] 完美转发,将变量 “还原” 为右值(右值引用类型)。
**面试答题的两个关键点: **
- 右值引用其实是一个右值,只是能够绑定到左值,所以调用的时候就会调用构造函数
- 完美转发里面含有一个有引用折叠,会根据你的实参类型进行相应的转换,右值还是右值,左值还是左值