一、右值引用:确定可移动,用 std::move
右值引用只绑定右值------临时对象,或调用者用 std::move 显式交出的对象。形参写成 A&&,函数就可以放心地把它当作可移动对象。但这份右值性不会自动传给下一层:
cpp
class A {};
void goo(A&& a) { std::cout << "goo(A&& a)" << std::endl; }
void goo(A& a) { std::cout << "goo(A& a)" << std::endl; }
void foo(A&& a) {
goo(a); // 调用 goo(A& a)
goo(std::move(a)); // 调用 goo(A&& a)
}
原因:a 的声明类型是 A&&,但它是一个具名变量,表达式 a 本身是左值。std::move 只做一次到右值的类型转换,让重载决议能选中 A&& 版本。
二、万能引用:可能可移动,用 std::forward
万能引用(T&&,且 T 由实参推导)绑定什么并不确定:传左值就绑左值,传右值才绑右值。所以转发时不能无条件地把它转成右值,否则左值实参也会被当作可移动对象掏空。
cpp
class A {};
void goo(A&& t) { std::cout << "goo(A&& t)" << std::endl; }
void goo(A& t) { std::cout << "goo(A& t)" << std::endl; }
template <typename T>
void foo(T&& t) {
goo(std::forward<T>(t));
}
void test() {
A a;
foo(a); // 最终调用 goo(A& t)
foo(A{}); // 最终调用 goo(A&& t)
}
std::forward<T> 做的是有条件的转换:
foo(a):T推导为A&,forward<A&>返回左值 →goo(A&);foo(A{}):T推导为A,forward<A>返回右值 →goo(A&&)。
三、对照
| 形参 | 绑定的实参 | 转发方式 | 转换 |
|---|---|---|---|
A&& |
只有右值 | std::move |
无条件 |
T&&(推导) |
左值或右值 | std::forward<T> |
有条件 |
两者都只是类型转换,不搬运资源;真正干活的是移动构造和移动赋值。用反了都有代价:对万能引用无条件 move,会掏空调用者还要用的左值;对右值引用忘记 move,右值性丢失,轻则退化成拷贝,重则找不到可调用的重载。