31、左值、右值详解
左值、右值不是“变量的永久标签”,也不是简单地看它写在等号哪边。它们描述的是一个表达式怎样标识对象、怎样参与初始化与重载决议。先分清表达式类别,再理解引用与移动,许多误区就会消失。
三种基本类别与两个组合类别
本文以 C++17 为基线:每个表达式属于 lvalue、xvalue、prvalue 三种基本类别之一。
- lvalue:广义左值中不是将亡值的那一部分,常见例子是普通变量名字表达式 x。
- xvalue:能标识对象、其资源可被复用的广义左值,例如对普通对象调用 std::move 得到的表达式。
- prvalue:用于初始化对象或计算运算符所需值的表达式,例如整数常量 42。
glvalue 包含 lvalue 与 xvalue;rvalue 包含 xvalue 与 prvalue。因此 xvalue 同时属于 glvalue 与 rvalue,不能把五种类别画成五个互斥的盒子。

不要用“等号左边右边”代替定义
const int 对象的名字是左值,却不能被赋值;重载运算符的规则又可能与内建赋值不同。字符串字面量是左值数组表达式,也不能笼统说所有字面量都是纯右值。
表达式的类型与值类别也是不同属性。一个具名的 int&& 变量,其名字表达式仍然是左值;“声明类型为右值引用”不等于每次使用该名字都得到右值表达式。
std::move 不会自己移动资源
std::move 把合适表达式显式转换成 xvalue,让移动操作有机会被重载决议选中。是否真的移动,取决于随后调用了哪个构造或赋值等操作。
只写 auto&& alias = std::move(source) 是绑定引用,没有创建第二个 vector,也没有将 source 的资源转移给一个新对象。即使后续发生移动构造,源对象仍然存在,其生命周期不会因为名字里出现 move 就结束。
C++17:验证类别与绑定
#include <cassert>
#include <iostream>
#include <type_traits>
#include <utility>
#include <vector>
int main() {
int x = 10;
int&& named = 42;
static_assert(std::is_same_v<decltype((x)), int&>);
static_assert(std::is_same_v<decltype((named)), int&>);
static_assert(std::is_same_v<decltype(std::move(x)), int&&>);
std::vector<int> source{1, 2, 3};
auto&& alias = std::move(source);
assert(&alias == &source);
assert(source.size() == 3);
std::vector<int> destination(std::move(source));
assert(destination.size() == 3);
std::cout << destination.size() << '\n';
}
输出 3。代码不打印或断言 source 移动后的元素,因为不能把通用“有效但未指定”的移动后状态简化为所有类型、所有操作都保证为空。对某个特定容器操作若需要更强结论,应核对该操作的具体保证。
左值到右值转换与完美转发
标准术语“左值到右值转换”通常指读取 glvalue 所标识对象的值,例如 int copy = x 中对 x 的取值;它不是 std::move 的同义词。
在模板推导形成的转发引用中,std
#include <iostream>
#include <string>
#include <utility>
void inspect(const std::string&) { std::cout << "L\n"; }
void inspect(std::string&&) { std::cout << "R\n"; }
template <typename T>
void relay(T&& value) {
inspect(std::forward<T>(value));
}
int main() {
std::string text = "hello";
relay(text);
relay(std::move(text));
}
输出 L、R。这只观察重载选择,inspect 没有转移任何字符串资源,不能据此声称 text 已变空。
生命周期与常见误区
直接绑定到某些临时对象的局部 const 引用或右值引用,可以按规则延长该临时对象的生命周期;把引用经过函数参数、返回值层层转交,不会无限延长它。返回指向已销毁局部对象的引用是悬空,不是优化。
const 对象经过 std::move 后通常仍带 const,未必能调用需要非 const 右值引用的移动操作,可能退回复制。具名引用、值类别、所有权与生命周期要分别分析,不能用一句“右值马上销毁”概括。
标准依据
核对 C++17 值类别、引用初始化及 move 与 forward。这里提供可访问条款,不保留无法确认来源的书名或 PDF 页码。
