Appearance
左值/右值、移动语义、std::move、完美转发(最高频原理题)
一、左值 vs 右值 + 左值引用 vs 右值引用
左值(lvalue):有名字、可取地址、可持续到语句结束的表达式(变量、*p、数组元素、对象成员…)。 右值(rvalue):临时的、即将销毁、无持久名字的表达式(字面量、临时对象、函数返回临时值…);C++11 又把右值细分 纯右值 prvalue 与 将亡值 xvalue(可被 steal)。
cpp
int a; // a 左值
a = 42; // 42、临时右值
&a; // 可取地址 => 左值
std::move(a); // 把左值 a 转成右值引用(xvalue)| 类型 | 绑定对象 | 特点 |
|---|---|---|
T&(左值引用) | 只能绑左值 | 常用于修改实参、避免拷贝 |
const T& | 左值或右值 | 万能只读绑定(也能延长临时生命周期) |
T&&(右值引用) | 只绑右值 | 标志「可窃取资源」,用于移动构造/转发 |
cpp
void f(int&); // 左值
void f(int&&); // 右值
f(x); // x 左值 -> int&
f(42); // 右值 -> int&&二、移动语义(Move Semantics):为什么需要
老 C++:传/返回大对象总是深拷贝(临时对象被拷贝后销毁,浪费)。 C++11 引入移动:拿到一个「即将销毁的右值」时,把它的内部资源(指针/句柄)搬过来,把源置空,O(1) 而非 O(n) 拷贝。
cpp
class Buffer {
char* p_;
public:
Buffer(const Buffer& o) { // 拷贝:深拷贝 O(n)
p_ = new char[...]; std::copy(...);
}
Buffer(Buffer&& o) noexcept : p_(o.p_) { // 移动:偷指针
o.p_ = nullptr; // 源置空,保证其析构安全
}
};关键点:
- 移动构造/赋值必须是**带右值引用参数(T&&)**的成员。
- 源对象移动后应处于「有效但未指定」状态(通常置空/清空),仍可正常析构。
- 移动通常应 noexcept:这样 vector 扩容才能放心用移动(见 vector 手写里 move_if_noexcept)。
三、std::move 到底是什么?
std::move(x) 并不移动任何东西、不拷贝、不搬数据 —— 它只是 static_cast<T&&>(x):把 x 标记成右值,让后续匹配到移动(&&)函数而触发移动而非拷贝。真正的「搬移」发生在接受 T&& 的移动构造函数/赋值里。
cpp
template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
return static_cast<std::remove_reference_t<T>&&>(t);
}陷阱:
std::move之后源对象不应再被使用其值(属"被搬走"状态)。- 对内置类型 move 无意义(int 没有移动)。
return std::move(local);是反优化(编译器本身会按返回右值/移动,加了 move 反而阻碍 NRVO/移动省略)。这是常被用来考的最热门细节。
四、完美转发与引用折叠(进阶必考)
forwarding reference(转发引用 / universal reference)
「模板推导出来的 T&&」既能绑左值也能绑右值(这是模板特殊规则,与普通 T&& 只能绑右值不同):
cpp
template<typename T> void f(T&& x); // T 推导:
f(lvalue); // T = T& -> 参数变成 T& (左值引用)
f(rvalue); // T = T -> 参数变成 T&& (右值引用)原因即引用折叠(reference collapsing):T& && → T&、T&& & → T&、& &→&、&& &&→&&。一遇到 & 结果就是 &。
std::forward —— 真正「原样转发」
要让被转发的参数在内部再传给别的重载时保留其左右值身份,不能一律 std::move(那样全变右值),需 std::forward<T>(x):
cpp
template<typename T>
void wrapper(T&& arg) {
inner(std::forward<T>(arg)); // arg 是左值→转左值;是右值→再转右值
}move vs forward 记忆
std::move(x):无条件转右值。std::forward<T>(x):按推导结果条件性转(还我原貌),专门配转发引用。
万能示例(经典 emplace / make_shared 实现用到这正是因为它用完美转发透传构造参数)
cpp
template<typename T, typename... Args>
T factory(Args&&... args) {
return T(std::forward<Args>(args)...); // 好处:参数不被强制拷
}五、为什么返回移动 / vector 的 emplace/push_back 均摊
emplace_back(args...)→ 内部完美转发构造。vector::push_back若接收右值则移动搬入,避免临时深拷贝 → 对非平凡类型内存分配关键。- 排序/容器操作大量用到交换/移动能显著提速(std::sort 交换元素用 move)。
六、经典推导测试题(自测用,测脑回路)
cpp
int x;
auto& r1 = x; // int&
const auto& r2 = x;// const int&
auto&& r3 = x; // int&(forwarding 规则,绑左值)
auto&& r4 = 42; // int&&(绑右值)
auto a = x; // int (剥引用/const)
decltype(x) b = x; // int (保留)答对说明你已理解 auto 剥常引用 与 decltype 原样的区别。