一、定义
左值(Lvalue):有名字、可被取地址、生命周期稳定的值
右值(Rvalue):没有名字、临时产生、用完即销毁的值
所有C++表达式结果,只有两种身份:左值 或 右值。
二、左值 & 右值 详细区分
1. 左值 Lvalue
特点:
有变量名
可以取地址
可以出现在赋值号左边
内存持久,不会马上销毁
例子:
int a = 10; // a 是左值
int b = a; // a 是左值
&a; // 能取地址 = 左值实锤2. 右值 Rvalue
特点:
没有名字、临时值
不能取地址
不能放在赋值号左边
表达式结束立刻销毁
两类典型右值:
纯数值:10、3.14、'a'
表达式临时结果:x+1、func()返回的临时对象
例子:
int a = 10 + 5; // 10+5 是右值
10 = a; // 报错!右值不能被赋值
&(10+5); // 报错!右值不能取地址三、左值引用 & 右值引用(核心重点)
1. 左值引用 & (C++98 传统引用)
语法:类型& 变量
只能绑定左值,不能绑定普通右值。
int a = 10;
int& ra = a; // ✅ 合法:左值引用绑定左值
int& rb = 10; // ❌ 报错:左值引用不能绑右值const 左值引用(万能引用)
const 左值引用可以绑定左值、也可以绑定右值
const int& r = 100; // ✅ 合法这是函数参数大量用 const T& 的根本原因:兼容所有实参。
关键大坑:const左值引用虽然能接收右值,但只读不可修改,无法实现资源窃取,因此绝对不能作为移动构造函数的形参。
2. 右值引用 && (C++11 新增)
语法:类型&& 变量
专门绑定右值、临时对象,不能绑定普通左值。
int&& r1 = 100; // ✅ 绑定右值
int a = 10;
int&& r2 = a; // ❌ 报错:不能绑左值重要:右值引用变量本身是左值
看似矛盾,但:
100 是右值
int&& r = 100
r 这个变量,是左值!!!(有名字、可取地址)
这是移动语义、完美转发的底层根源:所有具名变量,一律是左值。
四、移动构造函数核心
1. 为什么移动构造必须用 T&&,不能用 const T&?
核心答案:
const T& 确实能接收右值,但它是只读常量引用移动构造的本质:窃取临时对象的堆资源、置空原对象
const 修饰的对象无法修改,不能窃取资源、不能置空原对象,完全做不了移动语义
结论:移动构造函数形参必须是 非const右值引用 T&&,唯一选择。
2. 拷贝构造 vs 移动构造 终极区别
// 拷贝构造:左右值都能接,只读,只能深拷贝
String(const String& other);
// 移动构造:只接右值,可修改,直接窃取资源,零拷贝
String(String&& other);五、std::move 原理与作用
move 不是移动,只是强制类型转换!
作用:无条件把任意左值,强制转换成右值
本质:static_cast<T&&>(val)
适用场景:明确不需要原对象时,主动触发移动构造,提升效率。
六、完美转发 std::forward 完整版
1. 核心定义
完美转发:模板函数中,保留实参原本的左值/右值属性,原样转发给下层函数,不丢失类型特性。
2. 前置知识:万能引用 + 引用折叠
模板中的 T&& 不是普通右值引用,是万能引用,可适配左右值:
引用折叠规则:
T& + & = T&T& + && = T&T&& + & = T&T&& + && = T&&
3. 关键问题:forward 能不能给非模板函数用?
语法上可以,业务上没必要
forward 本身是模板函数,任意场景都能调用,不限制目标函数是否为模板
普通函数参数类型固定(如
void func(Test&& x)),左右值属性确定此时用 forward 和直接 move 效果一致,纯属多余
forward 的唯一价值场景:模板万能引用 T&&
只有模板场景,实参左右值不确定,才需要 forward 动态保留属性,move 无法替代。
4. 为什么完美转发必须用 forward?(经典坑点)
模板接收右值后,参数变成具名变量(左值),丢失右值属性:
template<typename T>
void wrapper(T&& x)
{
another(x); // x是具名左值!哪怕原本传的是右值,只会触发拷贝
another(std::forward<T>(x)); // 还原原始属性,右值进右值、左值进左值
}5. move 和 forward 终极对比
std::move:无条件转右值,不管原本是什么类型,强制移动
std::forward:有条件转换,原汁原味保留左右值属性
七、最全代码对比(一眼看懂区别)
int main()
{
int a = 10;
// 1. 普通左值引用
int& r1 = a; // ✅
// int& r2 = 10; // ❌
// 2. const左值引用
const int& r3 = a; // ✅
const int& r4 = 10; // ✅
// 3. 右值引用
int&& r5 = 10; // ✅
// int&& r6 = a; // ❌
// 4. move强制转右值
int&& r7 = std::move(a); // ✅
}八、总结
左值:有名、可取地址、持久。变量都是左值。
右值:无名临时量、不可取地址、语句结束销毁。
左值引用&:只能绑左值,不能绑临时右值。
const左值引用:万能引用,左右值都能绑,但只读,无法实现移动语义。
右值引用&&:只绑临时右值,可修改资源,是移动构造唯一合法形参。
具名右值引用变量是左值,传参会丢失右值属性,是所有问题的根源。
std::move:仅将左值强转为右值,无实际移动,无条件转换。
std::forward:仅模板万能引用场景生效,完美保留实参左右值属性,实现完美转发。
forward非模板可用但无用,只有搭配T&&万能引用才有不可替代的价值。