C++ 中的 T&& 要区分两种情况:右值引用 和转发引用 。关键在于:T 是否是正在推导的模板参数。
1. 右值引用
当类型是确定的,&& 表示右值引用,通常可以绑定临时对象,或通过 std::move 转换后的对象。
cpp
int a = 10;
int&& r1 = 42; // 可以:42 是右值
int&& r2 = a; // 错误:a 是左值
int&& r3 = std::move(a); // 可以
可以粗略理解为:
- 左值 :有身份、可被持续引用的对象,例如变量
a。 - 右值:包括临时值(纯右值),以及被标记为"可移动"的对象表达式(将亡值)。
注意:
std::move本身不移动任何数据,只是把表达式转换为右值类别。真正的移动由后续的移动构造或移动赋值完成。
主要用途:移动语义
cpp
class Buffer {
public:
Buffer(Buffer&& other) noexcept; // 移动构造
Buffer& operator=(Buffer&& other) noexcept; // 移动赋值
};
移动操作可以接管另一个对象持有的资源,避免昂贵的深拷贝。
cpp
std::string a = "hello";
std::string b = std::move(a);
此后 a 仍是有效对象,但其内容是"有效但未指定"的状态:不应假定内容保持不变,也不保证为空。
2. 转发引用:参与推导的 T&&
在下面的函数模板中,T&& 是转发引用(也叫万能引用),既能接收左值,也能接收右值:
cpp
template<class T>
void f(T&& x);
调用时:
cpp
int a = 10;
f(a); // T 推导为 int&,参数类型最终为 int&
f(10); // T 推导为 int,参数类型最终为 int&&
引用折叠
上面的结果来自引用折叠规则:
| 组合 | 折叠结果 |
|---|---|
T& & |
T& |
T& && |
T& |
T&& & |
T& |
T&& && |
T&& |
口诀:只要有一个 &,结果就是 &;只有两个 && 才是 &&。
这些组合通常通过模板替换、类型别名或 decltype 形成,不能直接写成 int& &&。
不是所有模板里的 T&& 都是转发引用
cpp
template<class T>
void f(const T&& x); // 右值引用:带 const,不是转发引用
template<class T>
void f(std::vector<T>&& x); // 右值引用:形式不是 T&&
template<class T>
struct Box {
void g(T&& x); // 右值引用:T 在 Box 实例化时就已确定
};
转发引用必须同时满足:形式恰好是 T&&(不能带 const),并且 T 是当前函数模板调用中参与推导的模板类型参数。
另一个常见的转发引用是 auto&&:
cpp
int a = 10;
auto&& x = a; // int&
auto&& y = 10; // int&&
3. 容易忽略:有名字的右值引用,其表达式是左值
cpp
void consume(int& x) { /* ... */ }
void consume(int&& x) { /* ... */ }
void test(int&& x) {
consume(x); // 调用 consume(int&)
consume(std::move(x)); // 调用 consume(int&&)
}
虽然变量 x 的类型 是 int&&,但表达式 x 是左值(因为它有名字,可以被反复使用)。
声明类型和表达式的值类别不是一回事。
4. 完美转发:搭配 std::forward
如果要把参数继续传给别的函数,同时保留调用者传入时的左值/右值属性,就用 std::forward:
cpp
#include <utility>
void consume(int&) { /* ... */ }
void consume(int&&) { /* ... */ }
template<class T>
void wrapper(T&& x) {
consume(std::forward<T>(x));
}
int main() {
int a = 10;
wrapper(a); // 最终调用 consume(int&)
wrapper(10); // 最终调用 consume(int&&)
}
两者的区别:
std::move(x):无条件转换为右值类别。std::forward<T>(x):根据推导出的T,有条件地保留原始值类别。
总结
确定类型的 T&& 是右值引用;参与模板推导的 T&& 可能是转发引用。移动用 std::move,完美转发用 std::forward。
**修改说明:**
1. **格式**:把终端里的方框表格改成标准 Markdown 表格,加了标题层级和代码块语言标记。
2. **右值的定义**:原文"标记为可移动的对象表达式"没问题,补充了标准术语(纯右值、将亡值),方便读者对应到规范。
3. **"有效但未指定"**:移动后对象的状态,补上了标准里的说法。
4. **`const T&&` 的说明**:原文只写"右值引用",补了原因(带 `const` 就不满足转发引用的形式)。
5. **新增 `std::vector<T>&&` 示例**:这是另一个容易误判的写法。
6. **示例可编译**:`consume` 原来只有声明,没有函数体,直接编译会链接失败,补上了空函数体,并给完美转发的例子加了 `main`。
7. **细节**:删除了 `int, 参数` 中多余的空格,口径统一用"类型"和"表达式"区分声明与值类别。