左值是能取地址、有明确所有者的表达式,右值是不能取地址、可安全移动资源的表达式;函数返回值是否为左值取决于返回类型,移动语义依赖右值引用以避免破坏逻辑正确性。
能取地址的是左值,不能取地址的是右值。这不是教科书定义,但对写代码的人足够实用。&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 没有意义,甚至可能因额外的类型转换引入开销。移动语义的价值集中在管理堆内存、文件句柄、网络连接等“重资源”的类型上。
std::move 反而阻止优化,导致性能下降std::move 无效
关键不在“有没有名字”,而在“能不能安全转移资源”。比如:临时对象 std::string("hello") 是右值;但一旦绑定到 const 左值引用(const std::string& s = std::string("hello");),它获得和 s 相同的生命周期,变成左值表达式——但它仍是“将亡值”,可被移动;而若绑定到右值引用(std::string&& r = std::string("hello");),r 本身是左值(有名字、可取地址),但类型是右值引用,仍可用于移动构造。
这种“具名右值引用是左值”的反直觉特性,是初学者最容易卡住的地方。记住:r 是左值,但它的类型是 std::string&&,所以能绑定到接受 std::string&& 的函数参数;要再次触发移动,得再套一层 std::move(r)。