学习定位:现代 C++ 核心知识。移动语义是现代 C++ 高效返回对象、转移资源所有权和编写 RAII 类型的基础,必须理解 std::move 只是转换而不是实际移动。

移动语义允许将资源从一个对象“转移”到另一个对象,避免深拷贝。它依赖右值引用实现,是 C++11 性能优化的关键。

1. 为什么需要移动语义

传统 C++ 中,返回或传递大对象(如 vectorstring)会触发深拷贝,效率低下。

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 转为右值引用

下一节(高级应用) 完美转发