左值是能取地址、有明确所有者的表达式,右值是不能取地址、可安全移动资源的表达式;函数返回值是否为左值取决于返回类型,移动语义依赖右值引用以避免破坏逻辑正确性。

左值和右值最直观的判断标准
能取地址的是左值,不能取地址的是右值。这不是教科书定义,但对写代码的人足够实用。&a 合法 → a 是左值;&(x + y) 报错 → x + y 是右值。注意:函数返回值是否为左值,取决于返回类型——int& f() 返回左值,int f() 返回右值(C++11 起是纯右值),int&& f() 也返回右值(但它是具名的,属于“将亡值”)。
为什么移动语义必须用右值引用(&&)
因为只有右值(尤其是将亡值)才可被“搬走”资源而不影响逻辑正确性。左值默认有明确的所有者和后续使用预期,直接移动会破坏程序行为。编译器禁止把 std::vector 之后再用 v,不是因为语法限制,而是 v 此时处于有效但未指定状态——它的内部指针可能已被置空,再次 v.size() 可能返回 0,但不会崩溃;而如果没加 std::move,赋值操作符会走拷贝路径,代价高得多。
-
std::move不移动任何东西,它只是把左值强制转成右值引用类型(即“告诉编译器:我允许你把它当将亡值处理”) - 真正执行移动的是类的移动构造函数或移动赋值运算符,它们需要显式定义或由编译器生成(满足条件时)
- 如果类没有移动构造函数,即使写了
std::move(x),也会退化为拷贝
常见误用:把 std::move 当性能银弹
对小对象(如 int、std::pair)调用 std::move 没有意义,甚至可能因额外的类型转换引入开销。移动语义的价值集中在管理堆内存、文件句柄、网络连接等“重资源”的类型上。
- 返回局部对象时,编译器通常会自动启用返回值优化(RVO),此时
std::move反而阻止优化,导致性能下降 - 对 const 左值调用
std::move无效:const 对象无法被移动(移动操作符通常不接受 const 参数) - 移动后再次使用原对象,行为未定义——不是“一定崩溃”,而是“结果不可预测”,调试时极难复现
如何判断一个表达式是左值还是右值?看生命周期和绑定能力
关键不在“有没有名字”,而在“能不能安全转移资源”。比如:临时对象 std::string("hello") 是右值;但一旦绑定到 const 左值引用(const std::string& s = std::string("hello");),它获得和 s 相同的生命周期,变成左值表达式——但它仍是“将亡值”,可被移动;而若绑定到右值引用(std::string&& r = std::string("hello");),r 本身是左值(有名字、可取地址),但类型是右值引用,仍可用于移动构造。
立即学习“C++免费学习笔记(深入)”;
这种“具名右值引用是左值”的反直觉特性,是初学者最容易卡住的地方。记住:r 是左值,但它的类型是 std::string&&,所以能绑定到接受 std::string&& 的函数参数;要再次触发移动,得再套一层 std::move(r)。









