Skip to content

深度探索 C++ —— 移动语义

🧠 一、基本概念回顾:什么是“拷贝”?

在 C++ 中,对象(比如 std::vectorstd::string、自定义类)在赋值或传参时可能会发生 拷贝操作。 如:

cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1; // 这里发生了拷贝

上面这句 v2 = v1 就是拷贝。拷贝做了什么?

  • 分配一块新的内存空间;
  • v1 里面的所有元素复制到 v2 中;
  • 如果 v1 很大,比如有几百万个元素,这会非常耗时。

这就叫 深拷贝(deep copy),因为它不是简单复制指针,而是复制整个数据内容。

💰 二、深拷贝为什么“昂贵”?

深拷贝之所以“昂贵”,是因为:

  • 内存分配和释放的开销;
  • 数据复制的时间成本;
  • 如果是嵌套结构(如 vector<vector<int>>),开销会指数级上升。

🚀 三、移动语义(Move Semantics)是什么?

1. 引入背景

C++11 引入了 移动语义(move semantics),用来优化这种“昂贵的拷贝”。

核心思想:如果一个对象是临时的、马上就要销毁了(比如函数返回的临时对象),那我们就不需要复制它,而是直接“偷走”它的资源!

2. 举例说明

cpp
std::vector<int> createVector() {
    std::vector<int> temp = {1, 2, 3, 4, 5};
    return temp; // 返回的是一个临时对象
}

int main() {
    std::vector<int> v = createVector(); // 这里不会拷贝,而是“移动”
}

在这个例子中:

  • createVector() 返回的是一个临时对象(右值);
  • 在 C++11 之前,会调用拷贝构造函数;
  • 在 C++11 之后,会调用 移动构造函数(move constructor),直接“偷走”临时对象内部的资源(比如指针、内存地址),而不是复制数据。

❓ 四、什么是移动构造函数(Move Constructor)?

1. 定义

移动构造函数是一种特殊的构造函数,它的参数是右值引用(T&&):

cpp
class MyClass {
public:
    // 移动构造函数
    MyClass(MyClass&& other) noexcept {
        // 把 other 的资源“偷过来”
        // 然后让 other 不再拥有这些资源
    }
};

2. 它做了什么?

  • 不复制数据;
  • 把“资源所有权”从一个对象转移到另一个对象;
  • 原对象(被移动的)进入“有效但未定义状态”,不能再使用,但可以安全析构。

❓ 五、移动 vs 拷贝:区别总结

特性拷贝构造函数移动构造函数
参数类型const T&(左值)T&&(右值)
是否复制数据否(转移资源)
是否改变原对象是(原对象资源被“偷走”)
性能高开销(深拷贝)极低开销(指针转移)

❓六、什么是右值?什么是右值引用?

1. 左值 vs 右值

类型举例是否可以取地址是否可以修改
左值int a; a, x[i]
右值5, a + b, f()
  • 左值:有名字的对象,可以取地址;
  • 右值:临时对象,没有名字,不能取地址;
  • 移动语义只对右值生效。

2. 右值引用(T&&)

  • T&& 是一种引用类型,只能绑定到右值;
  • 它允许我们对右值进行操作,比如“偷走”它的资源;
  • 例如:MyClass&& obj = MyClass(); 是合法的。

🧪 七、移动构造函数 vs 拷贝构造函数

cpp
#include <iostream>
#include <vector>

class MyVector {
public:
    std::vector<int> data;

    // 构造函数
    MyVector(int size) {
        data.resize(size);
        std::cout << "构造\n";
    }

    // 拷贝构造函数
    MyVector(const MyVector& other) {
        data = other.data;
        std::cout << "拷贝构造\n";
    }

    // 移动构造函数
    MyVector(MyVector&& other) noexcept {
        data = std::move(other.data); // 把 other 的资源“偷过来”
        std::cout << "移动构造\n";
    }
};

MyVector createVector() {
    MyVector v(1000000); // 创建一个大对象
    return v;
}

int main() {
    MyVector a = createVector(); // 调用移动构造函数
}

输出可能是:

构造
移动构造

说明没有调用拷贝构造函数,而是直接移动了资源,非常高效!


📌 八、什么时候用 const&,什么时候用 &&?

场景推荐写法说明
读取但不修改对象const T&避免拷贝
接收临时对象并移动T&&移动语义,提升性能
接收左值并读取const T&左值不能绑定到 T&&
接收右值并移动T&&只能绑定到右值

🧩 九、完美转发(Perfect Forwarding)与 std::forward

在模板编程中,如果你希望一个函数参数既能接收左值又能接收右值,并且保持其左/右值属性,可以使用:

cpp
template<typename T>
void wrapper(T&& arg) {
    foo(std::forward<T>(arg)); // 保持 arg 的左/右值属性
}

这是高级用法,暂时不深入。


✅ 十、总结(一句话总结)

概念简单解释
深拷贝复制整个对象内容,很慢
移动语义把资源从一个对象“偷”到另一个对象,很快
移动构造函数专门用于右值的构造函数,避免拷贝
右值引用 &&只能绑定到临时对象(右值)
常量引用 const&可以绑定到左值和右值,不能修改对象

如果你理解了这些内容,恭喜你已经掌握了 C++11 中最核心的性能优化机制之一:移动语义