一、强烈推荐使用右值引用的场景
1. 实现移动构造函数和移动赋值运算符(核心场景)
class MyBuffer {
public:
// ✅ 移动构造函数
MyBuffer(MyBuffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 把源对象置空
other.size_ = 0;
}
// ✅ 移动赋值运算符
MyBuffer& operator=(MyBuffer&& other) noexcept {
if (this != &other) {
delete[] data_; // 释放自己的旧资源
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
private:
char* data_;
size_t size_;
};
为什么推荐:这是移动语义存在的根本原因,能显著提升性能。
上面的还有简化实现方式:
class MyBuffer {
public:
// 移动赋值运算符(极简版)
MyBuffer& operator=(MyBuffer&& other) noexcept {
if (this != &other) {
// 借用 swap,利用移动构造的临时对象
MyBuffer temp(std::move(other));
swap(temp);
}
return *this;
}
private:
void swap(MyBuffer& other) noexcept {
std::swap(data_, other.data_);
std::swap(size_, other.size_);
}
};
2. 实现完美转发(万能引用 + std::forward)
// ✅ 万能引用 + std::forward 实现完美转发
template <typename T>
void wrapper(T&& arg) { // T&& 是万能引用
target(std::forward<T>(arg)); // 保持左值/右值属性
}
// 应用场景:工厂函数、代理模式、装饰器模式
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
为什么推荐:让参数以最合适的方式(拷贝或移动)传递,避免不必要的拷贝。
3. 重载函数以优化右值参数
class Container {
std::vector<int> data_;
public:
// ✅ 接收左值:拷贝
void push_back(const int& value) {
data_.push_back(value);
}
// ✅ 接收右值:移动(避免拷贝)
void push_back(int&& value) {
data_.push_back(std::move(value));
}
};
// 使用
Container c;
int x = 42;
c.push_back(x); // 调用左值版本(拷贝)
c.push_back(100); // 调用右值版本(移动)
c.push_back(std::move(x)); // 调用右值版本(移动)
为什么推荐:给调用者提供选择,用移动优化临时对象和 std::move 的场景。
4. 移动容器元素(转移所有权)
std::vector<std::string> strings;
std::string s = "hello";
// ✅ 移动插入,避免拷贝
strings.push_back(std::move(s)); // s 被移走,变为空
// 或者从临时对象插入
strings.push_back("world"s); // 右值,自动移动
为什么推荐:容器操作是性能热点,移动比拷贝快得多。
5. 转移独占资源(unique_ptr 等)
std::unique_ptr<MyClass> createObject() {
auto ptr = std::make_unique<MyClass>();
// ... 初始化
return ptr; // ✅ 自动移动(或 NRVO)
}
// 转移所有权
std::unique_ptr<MyClass> obj1 = createObject();
std::unique_ptr<MyClass> obj2 = std::move(obj1); // ✅ obj1 变为空
为什么推荐:std::unique_ptr 只能移动不能拷贝,右值引用是实现它的基础。
6. 自定义移动迭代器
std::vector<std::string> source = {"a", "b", "c"};
std::vector<std::string> dest;
// ✅ 移动迭代器:把 source 的元素移动到 dest
std::move(source.begin(), source.end(), std::back_inserter(dest));
// source 中的元素被移走,变为空字符串
为什么推荐:批量移动比循环移动更高效。
二、不推荐使用右值引用的场景
❌ 1. 对基本类型使用右值引用
// ❌ 完全没有意义
void process(int&& x) { }
int a = 42;
process(a); // ❌ 编译错误(左值)
process(std::move(a)); // ✅ 能编译,但毫无意义
// 基本类型拷贝和移动成本相同,没必要用右值引用
更好的写法:直接用值传递或 const 引用。
❌ 2. 在函数返回值中使用 std::move
// ❌ 错误用法(阻止 NRVO)
MyClass createObject() {
MyClass obj;
return std::move(obj); // 阻止返回值优化(RVO/NRVO)
}
// ✅ 正确写法
MyClass createObject() {
MyClass obj;
return obj; // 编译器自动应用移动(或 NRVO)
}
原因:C++17 保证复制省略(Copy Elision),return obj 是最高效的写法。
❌ 3. 移动后继续使用对象
std::string s = "hello";
std::string t = std::move(s);
// ❌ 危险!s 被移走后处于"有效但未指定"状态
std::cout << s; // 可能崩溃或输出空字符串
s.size(); // 可能返回 0,也可能未定义行为
// ✅ 正确做法:移动后不再使用,除非重新赋值
s = "new value"; // 重新赋值后可以安全使用
原因:被移动的对象处于"有效但未指定"状态,访问它是不安全的。
❌ 4. 在 const 对象上使用 std::move
const std::string s = "hello";
auto t = std::move(s); // ❌ 编译失败或退化为拷贝
// std::move(const T&) 返回 const T&&
// const T&& 只能绑定到 const T&& 参数,通常不存在这样的重载
// 最终会退化为拷贝构造
原因:不能从 const 对象移动,因为移动需要修改原对象。
❌ 5. 对生命周期很短的局部变量
void process(std::string&& s) { }
void example() {
// ❌ 过度设计
std::string s = "hello";
process(std::move(s)); // 因为 process 是右值版本
// 之后 s 被移走,无法再用
// ✅ 简单写法
process("hello"s); // 直接传临时对象,自动移动
}
原因:临时对象本身就是右值,不需要额外的 std::move。
三、快速决策表
| 场景 | 是否推荐 | 说明 |
|---|---|---|
| 移动构造函数/赋值运算符 | ✅ 强烈推荐 | 移动语义的核心 |
完美转发(T&& + std::forward) |
✅ 强烈推荐 | 泛型编程必备 |
| 函数重载优化(左值/右值版本) | ✅ 推荐 | 给调用者提供选择 |
容器插入(push_back(std::move(x))) |
✅ 推荐 | 性能提升明显 |
unique_ptr 转移所有权 |
✅ 推荐 | 只能靠移动 |
| 返回局部对象时 | ❌ 不要用 std::move
|
会阻止 NRVO,用 return obj;
|
基本类型(int、double) |
❌ 不推荐 | 拷贝和移动成本相同 |
| 移动后继续使用 | ❌ 危险 | 未定义行为 |
| const 对象 | ❌ 无效 | 不能从 const 移动 |
| 作为函数参数类型 | ⚠️ 谨慎 | 一般用值传递 + std::move 更好 |
四、一句话总结
使用右值引用的黄金法则:用于"转移资源所有权"(移动构造、完美转发、容器优化),不要用于"传递基本类型"或"返回局部对象时加 std::move"。移动后,把源对象当作"空壳",不再访问。