左值: 可以赋值、取地址的值
右值:字面值、临时值等无法取地址的值
左值引用: 可以指向左值,不能指向右值的引用。const & 左值可以指向右值,
1 | const int &ref_a = 5; // 编译正常 |
右值引用: 可以指向右值,不能指向左值的引用。
右值引用可以修改右值
右值引用可以指向左值 – std::move
1 | int a = 10; |
std::move 只是强制把左值转换成右值,让右值引用可以指向左值,相当于强制类型转换;
- 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
- 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
- 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。
应用场景,实现容器的移动。增加了移动构造函数
、移动赋值重载函数
,
std::move 解析
1 | /** |
std::move是个模板函数,constexpr
表示这个函数是编译期常量,在编译期间求值,不会增加运行时开销;typename
常在模板函数中使用,
告诉编译期,后边跟的std::remove_reference<_Tp>::type
是个类型。
std::remove_reference
有3个定义, 作用是移除引用,返回值本身:
1 | /// remove_reference |
因此return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
就是强制转换成右值引用返回。
完美转发, std::forward
1 | /** |
这里用到了引用折叠,也就是多个引用类型叠加时,最后的类型是什么?引用有左值引用、右值引用,叠加后有4种状态:
- 左引 - 左引: T& & – 左引
- 左引 - 右引: T& && – 左引
- 右引 - 左引: T&& & – 左引
- 右引 - 右引: T&& && – 右引
折叠的规则是: 有1个左引,最后就是左引。
完美转发经典场景:
1 | template <class... Args> |
- std::move 强制转换成右值
- std::forward
是什么值,就转成什么值,中间过渡传递的。它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。