在 C++ 中,lvalue(左值)是指向内存中有名称并可以引用的对象的表达式(例如变量)。rvalue(右值)是指没有名称、且在传统意义上不是对象的临时值(例如,结果 a + b,类型为 5 的字面量)。
lvalue 可以取地址,而 rvalue 不能(在引入 rvalue 引用之前)。用于将它们传递到函数中有:
void foo(const std::string& s); — 接收 lvalue 和 rvalue。void bar(std::string& s); — 仅接收 lvalue。void baz(std::string&& s); — 仅接收 rvalue。示例:
void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue
rvalue 引用用于高效传递临时对象,主要用于移动语义,以便移动资源而不是复制它们。
表达式
std::move(obj)会获得什么类型的引用(lvalue 还是 rvalue)?应用 std::move 后,对象会变成什么类别?
答案:
std::move(obj) 始终返回 rvalue 引用(T&&),但对象本身仍然是 lvalue,只是进行了显式转换。之后对这个对象的操作要非常小心(它可能处于不确定但有效的状态)。
示例:
std::string s = "data"; std::string d = std::move(s); // d 获取 s 的数据,s 现在为空
故事
在一个大型项目中,一位开发者通过 lvalue 引用 T& 传递临时对象(而不是 T&& 或 const T&)。这导致了编译错误和不优化的复制——移动语义未被使用,性能降低了 40%。
故事
在前端引擎中错误地将 std::move 应用于随后又使用的变量,导致变量处于 "破坏" 状态,引发了崩溃和渲染线程崩溃。
故事
在序列化库中将类型为 std::vector<T> 的容器作为 lvalue 传递给函数,而期待进行移动。结果是进行了大量元素的昂贵复制,严重降低了大数组的序列化时间。