一、移动语义的核心原理

移动语义解决了C++03时代的一个根本问题:按值传参和返回值时的深拷贝代价。理解右值引用(&&)和移动构造/赋值,是写出零开销抽象的基础。

1.1 左值 vs 右值 vs 亡值

// C++11将表达式分为三类:
//
// ① 左值(lvalue):有持久地址,可取地址
//    int x; x = 5;     // x是左值
//    std::string s; s = "hello";  // s是左值
//
// ② 亡值(xvalue):即将销毁的对象,可以"抢走"资源
//    std::move(x)  // 转为xvalue
//    std::unique_ptr(new int(42))  // 临时智能指针
//
// ③ 纯右值(prvalue):即将销毁的临时对象
//    42 + 1          // 纯右值
//    func_returning_string()  // 临时string

// 移动构造:只"偷"指针,不深拷贝
class Buffer {
    char* data_;
    size_t size_;
public:
    // 移动构造:接管源对象的资源,原对象析构时不会重复释放
    Buffer(Buffer&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;  // 源对象析构时data_为nullptr,不释放
        other.size_ = 0;
    }

    // 移动赋值:先释放自己的资源,再"偷"别人的
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data_;      // 先释放自己
            data_ = other.data_; // 再"偷"
            size_ = other.size_;
            other.data_ = nullptr;
        }
        return *this;
    }
};

// Benchmark:移动 vs 拷贝
// std::vector v1(1000000, 1);
// v2 = v1;            // 深拷贝:~10ms
// v3 = std::move(v1); // 移动:~0.01ms  ← 1000倍差距!

二、完美转发与通用引用

2.1 T&&不是总是右值引用

// 模板参数推导中的T&&:
template
void func(T&& x) {
    // T=int,  x是int&&(右值引用)
    // T=int&, x是int&(左值引用!)
    // ⚠️ T&& 在模板中被称为"通用引用"
    // 它可以是左值也可以是右值,取决于传入的参数

    // forward(x):完美转发,保持原始值类别
    g(std::forward(x));
}

int a;
func(a);      // T=int&, 调用g(int&)
func(std::move(a)); // T=int,  调用g(int&&)

template
void dispatch(std::string name, Args&&... args) {
    // args是通用引用包
    // 用std::forward_as_tuple完美转发所有参数
    Executor::get().execute(name, std::forward_as_tuple(args...));
}

2.2 返回值优化(RVO/NRVO)

// ⚠️ 常见误区:return std::move(obj)反而可能禁用RVO
struct BigData {
    std::vector v;
    BigData() { v.reserve(1000000); }  // 构造函数
    BigData(const BigData&) { /* 深拷贝 */ }  // 拷贝构造
    BigData(BigData&&) noexcept { /* 移动 */ }  // 移动构造
};

// 编译器强制RVO(NRVO):
// 如果返回值是局部对象,编译器可以在返回值位置直接构造
BigData create() {
    BigData d;    // ← 编译器直接在这里构造返回值
    d.v.push_back(1);
    return d;      // 无拷贝无移动
}
// → 即使有移动/拷贝构造函数,也不会调用

// ⚠️ return std::move(d) 可能害处:
// 禁用NRVO → 变成"拷贝/移动到返回值"
// 只在函数返回局部对象的引用时可能有用
// (但这本身是悬垂引用的危险操作)

三、智能指针与移动语义

#include 

// unique_ptr:独占所有权的智能指针,禁止拷贝
std::unique_ptr p1(new int(42));
// std::unique_ptr p2 = p1;  // ❌ 编译错误
std::unique_ptr p2 = std::move(p1);  // ✅ 移动所有权
// p1变为空,p2持有int(42)

// unique_ptr用作类成员时的最佳实践
class Service {
    std::unique_ptr config_;  // ✅ 独占
    std::shared_ptr db_;     // ✅ 可共享

public:
    Service() : config_(std::make_unique()) {}
    void set_database(std::shared_ptr db) {
        db_ = std::move(db);  // 接受shared_ptr所有权
    }
};

// shared_ptr的移动(引用计数不变,转移所有权)
std::shared_ptr sp1 = std::make_shared();
// shared_ptr sp2 = sp1;    // 引用计数+1
std::shared_ptr sp2 = std::move(sp1);  // 引用计数不变!
// sp2.use_count() = 1, sp1.use_count() = 0, sp1=nullptr

四、emplace与原地构造

// push_back vs emplace的性能差异
std::vector v;

// push_back:先构造临时string,再拷贝/移动到容器
v.push_back(std::string("hello"));  // 构造 + 移动

// emplace_back:直接在容器内构造,省去中间步骤
v.emplace_back("hello");  // 直接构造,无中间对象

// ⚠️ emplace_back的类型推导问题
struct Point { int x, y; };

std::vector points;
points.emplace_back(1, 2);  // 正确:就地构造Point(1,2)

// ❌ 陷阱:
points.emplace_back({1, 2}); // 先构造initializer_list,再从initializer_list构造Point
// 实际调用:Point(initializer_list),不是Point(int, int)!

// 正确做法:
points.emplace_back(1, 2);        // ✅ 推荐
points.push_back(Point{1, 2});   // ✅ 显式构造
points.push_back({1, 2});        // ⚠️ 可能有歧义

// 性能测试(100万次插入):
// push_back(string):  120ms(1次构造+1次移动)
// emplace_back:       45ms(1次构造) ← 2.7倍提速