输入关键词开始搜索

C++ 移动语义

为什么需要移动语义

// 拷贝:深复制所有数据 → 慢
vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = a;    // 5 个 int 拷贝 + 堆分配
// a 还在,b 有独立副本

// 移动:偷走资源 → 快
vector<int> c = std::move(a);  // 偷走 a 的指针,a 变空
// a 被"掏空",c 接管了原 a 的堆内存(零拷贝)

核心思想:不需要保留原对象时,直接转移资源而非拷贝

左值 vs 右值

int x = 42;          // x 是左值(有名字、可取地址)
int &ref = x;        // 左值引用

int &&rref = 42;     // 右值引用 — 绑定到临时对象
int &&rref2 = x + 1; // x+1 是临时结果,也是右值

// 判断口诀:
// 能取地址 → 左值
// 临时值、字面量、函数返回的非引用 → 右值

移动构造与移动赋值

class Buffer {
    char *data;
    size_t size;
public:
    // 普通构造
    Buffer(size_t n) : data(new char[n]), size(n) {}

    // 拷贝构造(深拷贝)
    Buffer(const Buffer &other)
        : data(new char[other.size]), size(other.size) {
        memcpy(data, other.data, size);
    }

    // 移动构造(偷资源)⚠️ noexcept 很关键!
    Buffer(Buffer &&other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;   // 原对象置空,防止 double free
        other.size = 0;
    }

    // 移动赋值
    Buffer &operator=(Buffer &&other) noexcept {
        if (this != &other) {
            delete[] data;          // 释放自己的旧资源
            data = other.data;      // 偷
            size = other.size;
            other.data = nullptr;   // 清空原对象
            other.size = 0;
        }
        return *this;
    }

    ~Buffer() { delete[] data; }
};

// 使用
Buffer createBuffer() { return Buffer(1024); }  // 返回值优化/移动
Buffer buf = createBuffer();   // 移动构造,不是拷贝

std::move 到底做了什么

// std::move 的本质:无条件转换为右值引用
// 它不移动任何东西,只是"标记"可以移动

template <typename T>
constexpr remove_reference_t<T> &&move(T &&t) noexcept {
    return static_cast<remove_reference_t<T> &&>(t);
}

string s1 = "hello";
string s2 = std::move(s1);  // s1 被"掏空"
// std::move 只是把 s1 转成 string&&,
// 真正移动的是 string 的移动构造函数

完美转发

// 问题:模板参数 T&& 既接收左值也接收右值,但转发时丢失了"左右值"信息
template <typename T>
void wrapper(T &&arg) {
    // arg 有名字 → 是左值!
    // target(arg) 永远走拷贝,不会走移动
    target(arg);                     // ❌ 总是左值
    target(std::forward<T>(arg));    // ✅ 保持原始值类别
}

// std::forward: 左值 → 左值引用,右值 → 右值引用
// 配合 T&& (转发引用/万能引用) 使用

实际收益

// 场景:函数返回大对象
vector<string> buildList() {
    vector<string> v;
    v.push_back("hello");           // 拷贝 C 字符串
    v.push_back(std::move(someStr)); // 移动 string → 免拷贝
    return v;                        // RVO/NRVO — 编译器优化,甚至不需要移动
}

// 场景:容器存放不可拷贝对象
vector<unique_ptr<int>> pointers;
auto p = make_unique<int>(42);
pointers.push_back(std::move(p));  // unique_ptr 只能移动

规则总结

规则说明
三五法则定义了析构/拷贝/移动中的一个 → 考虑定义全部五个
noexcept移动构造/赋值必须加 noexcept,否则 vector 扩容时退化为拷贝
move 后别用std::move(x) 后,x 处于”有效但未指定”状态,只能析构或赋值
RVO 优于 movereturn std::move(x) 反而阻止编译器优化,直接 return x