学习定位:现代 C++ 核心知识。移动语义是现代 C++ 高效返回对象、转移资源所有权和编写 RAII 类型的基础,必须理解 std::move 只是转换而不是实际移动。
移动语义允许将资源从一个对象“转移”到另一个对象,避免深拷贝。它依赖右值引用实现,是 C++11 性能优化的关键。
1. 为什么需要移动语义
传统 C++ 中,返回或传递大对象(如 vector、string)会触发深拷贝,效率低下。
std::vector<int> create() {
std::vector<int> v(1000000);
return v; // C++98 深拷贝,C++11 移动
}移动语义让资源的所有权转移,源对象被置于“有效但未指定”的状态(通常为空)。
2. 移动构造函数与移动赋值运算符
2.1 语法
class MyClass {
public:
// 移动构造
MyClass(MyClass&& other) noexcept
: data_(other.data_)
{
other.data_ = nullptr; // 源对象放弃资源
}
// 移动赋值
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data_; // 释放当前资源
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
private:
int* data_;
};2.2 noexcept
- 移动操作应标记
noexcept,以便标准库容器在重新分配时使用移动而非拷贝(保证强异常安全)。
2.3 何时调用移动构造/赋值
- 函数返回局部对象(优先移动,或 RVO)
- 使用
std::move显式转换 - 临时对象作为参数(按值传参时)
- 容器操作(如
push_back(std::move(x)))
3. std::move 的本质
std::move 并不移动任何东西,它只是一个类型转换:将左值转换为右值引用,从而触发移动语义。
template<typename T>
decltype(auto) move(T&& arg) {
return static_cast<remove_reference_t<T>&&>(arg);
}使用示例:
std::string a = "hello";
std::string b = std::move(a); // 移动构造,a 变空
// 此后 a 仍可赋值或析构,但不建议直接使用其内容4. 移动语义与拷贝语义对比
| 操作 | 时间复杂度 | 资源处理 | 源对象状态 |
|---|---|---|---|
| 拷贝 | O(n) | 深拷贝 | 不变 |
| 移动 | O(1) | 转移指针 | 有效但未指定(通常为空) |
5. 移动语义与右值引用的关系
- 右值引用是语法工具,用于识别可移动的对象。
- 移动语义是行为,通过右值引用参数实现资源转移。
- 移动构造函数参数为右值引用,因此只能从右值(临时对象或
std::move转换后的左值)构造。
6. 最佳实践
- 默认启用移动:当类管理动态资源时,应提供移动构造/赋值(可
= default如果所有成员可移动)。 - 标记
noexcept。 - 不要显式
std::move返回值(会抑制 RVO)。 - 使用
std::move仅当不再需要源对象。 - 传递大对象给函数时,若不再使用,用
std::move转为右值引用。
下一节(高级应用) 完美转发