01 左值和右值的基本概念
-
左值
一般来说,可以取地址的,有名字的就是左值。如:int a = 10; // a 左值; 10 右值 int b = a + 10; // b 左值; a + 10 右值
-
右值
不能取地址,没有名字的就是右值。右值又分为以下两种:- 将亡值
将被移动的对象,如返回右值引用 T&& 的函数的返回值;std::move 的返回值。 - 纯右值
返回类型非引用的函数返回的临时变量的类型;运算符表达式的值;不跟对象关联的字面值常量。
- 将亡值
-
左值引用和右值引用
int n = 10; int & l_n = n; // 左值引用 int && r_n = std::move(l_n); // 右值引用
03 引用的新规则
-
引用折叠规则
如果间接的创建一个引用的引用,将会造成这些引用的折叠:-
所有的右值引用折叠到右值引用上仍然是一个右值引用:
T && && => T &&
-
所有的其他类型的引用的引用都将变成左值引用:
T & & => T & T & && => T & T && & => T &
引用折叠的根本原因是 C++中禁止 reference to reference。
可以简单的认为所有包含左值引用的引用的引用都会折叠成左值引用。
-
-
右值引用的特殊类型推断原则
当将一个左值传递给一个参数是右值引用的模板函数时,编译器会推断模板参数的类型为实参的左值引用:template<typename T> void func(T &&) {} int n = 10; func(10);
-
可以通过 static_cast 显式的将一个左值转换成一个右值
int n = 10; int &qln = n; // int && qrn = qln; // 无法将右值引用绑定到左值 int && qrn = static_cast<int &&>(qln); // Ok
04 std::move
C++11 基于安全的原因具名参数即便是右值引用也不会被认定为右值,必须使用 std::move 来获取右值。
void isRValue(const int& num) { std::cout << "left ref" << std::endl; }
void isRValue(const int&& num) { std::cout << "right ref" << std::endl; }
int main()
{
int&& a = 5;
isRValue(a); // left ref
isRValue(std::move(a)); // right ref
}
sstd::move 作用是将参数无条件的转换成的右值引用。
标准库中的 move 定义:
template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&&
move(_Ty&& _Arg) noexcept
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
其中 std::move函数的参数 _Ty && 是一个指向模板类型的右值引用,由于模板中的右值引用可以接收左值和右值,因此 std::move函数的参数既可以是一个右值也可以是一个左值。
而且 std::move 函数实际上是通过 static_cast 返回右值的因此其本质上和 static_cast<_Ty &&>(_Arg)
没有区别。
05 std::forward
std::forward 作用是返回该显式实参类型的右值引用,以保证参数在传递过程中保持其属性,又被称为完美转发。
std::forward 不仅可以保持左值和右值属性不变,还同时可以保持 const, l_reference, r_reference, validate 等属性不变。
void isRValue(const int& num) { std::cout << "left ref" << std::endl; }
void isRValue(const int&& num) { std::cout << "right ref" << std::endl; }
int main()
{
int&& a = 5;
isRValue(a); // left ref 实参类
isRValue(std::forward<int>(a)); // right ref 实参类型为 int&&
}