# 左值引用与右值引用的区别
面试官问"左值引用和右值引用有什么区别",很多人能答出"一个绑定左值一个绑定右值",但接着追问"右值引用解决了什么问题""std::move 到底做了什么"就卡住了。根本原因是没理解右值引用背后的资源转移思想。
# 简要回答
左值引用 T& 绑定有名字、有地址的持久对象,用来避免拷贝;右值引用 T&& 绑定临时对象或将亡值,让编译器可以把资源"偷"过来而不是深拷贝,这就是移动语义。std::move 本身不移动任何东西,它只是把左值强转成右值引用,告诉编译器"这个对象我不要了,你可以搬走"。
# 详细回答
绑定规则
左值引用只能绑定左值——有名字、可以取地址的对象,比如变量 int x、数组元素 arr[0]。右值引用只能绑定右值——临时对象 string("hello")、字面量 42、函数返回的临时值。特殊情况:const T& 可以绑定右值,这是 C++11 之前唯一能接住临时对象的方式,但它是只读的,没法"偷"资源。
为什么需要右值引用
C++11 之前,vector<string> v; v.push_back(getString()); 这行代码会把 getString() 返回的临时 string 深拷贝一份再放进 vector,临时对象随后销毁。明明这个临时对象马上就要死了,它内部的堆内存完全可以直接接管过来,深拷贝纯属浪费。右值引用就是为了让编译器识别出"这个对象即将销毁",从而触发移动构造而非拷贝构造。
std::move 的本质
std::move(x) 等价于 static_cast<T&&>(x),它不移动任何数据,只是把 x 的类型从左值转成右值引用。真正的资源转移发生在移动构造函数里——把源对象的指针拿过来,把源对象的指针置空。调用 std::move 后源对象处于"有效但未指定"状态,不能再假设它的值。

# 知识拓展
Q:引用折叠规则是什么?
模板或 typedef 中出现"引用的引用"时,编译器按规则折叠:只有 T&& && 折叠成 T&&,其余三种组合(T& &、T& &&、T&& &)全部折叠成 T&。口诀:有左值引用就折叠成左值引用。
Q:万能引用和右值引用怎么区分?
写法一样都是 T&&,但万能引用出现在模板类型推导上下文中(template<typename T> void f(T&& arg) 或 auto&& x)。如果 T 是具体类型比如 string&&,那就是普通右值引用。万能引用配合 std::forward 实现完美转发——传进来是左值就转发左值,传进来是右值就转发右值。
Q:为什么移动构造要加 noexcept?
vector 扩容时需要把旧元素搬到新内存。如果移动构造可能抛异常,搬到一半抛了,已经搬走的元素回不去了,数据就坏了。所以标准库看到移动构造没标 noexcept,就退回用拷贝构造来保证异常安全。加了 noexcept 才能享受移动带来的性能提升。
Q:移动后的对象还能用吗?
能用,但不能假设它的值。标准保证它处于"有效但未指定"状态,可以赋新值、可以析构,但不能读它的内容当作有意义的数据。
评论
验证登录状态...