深度探索 C++ —— 移动语义
🧠 一、基本概念回顾:什么是“拷贝”?
在 C++ 中,对象(比如 std::vector
、std::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 中最核心的性能优化机制之一:移动语义。