很多人在学习 C++ 时,第一次看到下面这两个函数会一脸懵:
cpp
Box(Box&& other); // 移动构造
Box& operator=(Box&& other); // 移动赋值
这两个函数看起来像"高级语法",
但实际上它们不是凭空出现的,而是被 性能问题 + 临时对象浪费 一步步逼出来的进化结果。
本文从"问题"出发,再用一个完整可运行的例子把它讲透。
一、最初阶段:只有拷贝构造
最早 C++ 只有拷贝语义:
cpp
Box(const Box& other);
含义就是:
cpp
复制一份资源
示例:
cpp
Box a(10);
Box b = a; // 拷贝构造
如果资源很小,问题不大。
但当成员是:
std::stringstd::vector- 大数组
- 文件句柄
- 网络连接
复制就会变得 慢 + 占内存。
二、问题升级:临时对象的浪费
看一个常见函数:
cpp
Box createBox() {
Box b(10);
return b;
}
调用:
cpp
Box a = createBox();
如果只有拷贝构造,流程是:
cpp
构造 b
拷贝构造 a
析构 b
问题在于:
b 马上要死了,还完整复制一份资源,纯浪费。
于是一个需求出现了:
能不能不复制,而是"搬走资源"?
三、移动语义诞生 ------ "搬家"而不是"复印"
移动语义的核心思想:
如果对象马上要被销毁,就不要复制资源,直接转移所有权。
这就引出了 移动构造 和 移动赋值。
四、完整示例代码(核心)
下面是一个完整可运行的类,包含:
- 构造
- 拷贝构造
- 移动构造
- 拷贝赋值
- 移动赋值
- 析构
cpp
#include <iostream>
using namespace std;
class Box {
public:
int* data;
// 构造
Box(int v) {
data = new int(v);
cout << "构造\n";
}
// 拷贝构造
Box(const Box& other) {
data = new int(*other.data);
cout << "拷贝构造\n";
}
// 移动构造
Box(Box&& other) {
data = other.data; // 接管资源
other.data = nullptr; // 清空对方
cout << "移动构造\n";
}
// 拷贝赋值
Box& operator=(const Box& other) {
cout << "拷贝赋值\n";
if (this == &other) return *this;
delete data;
data = new int(*other.data);
return *this;
}
// 移动赋值
Box& operator=(Box&& other) {
cout << "移动赋值\n";
if (this == &other) return *this;
delete data; // 释放旧资源
data = other.data; // 接管资源
other.data = nullptr; // 清空对方
return *this;
}
~Box() {
delete data;
cout << "析构\n";
}
};
Box createBox() {
Box b(10);
return b;
}
int main() {
cout << "=== 移动构造示例 ===\n";
Box a = createBox(); // 触发移动构造(或RVO)
cout << "=== 移动赋值示例 ===\n";
Box b(1);
b = createBox(); // 触发移动赋值
}
五、可能看到的输出
不同编译器略有差异,常见输出:
cpp
=== 移动构造示例 ===
构造
移动构造
析构
析构
=== 移动赋值示例 ===
构造
构造
移动赋值
析构
析构
有时你只会看到:
cpp
构造
那是 RVO(返回值优化) 在工作 ------
编译器直接在目标位置构造对象,连移动都省掉了。
六、移动构造 vs 移动赋值区别
| 类型 | 场景 | 是否已有资源 | 核心动作 |
|---|---|---|---|
| 移动构造 | 新对象创建 | 没有 | 直接接管资源 |
| 移动赋值 | 对象已存在 | 有 | 先释放再接管 |
七、为什么要把对方清空?
cpp
other.data = nullptr;
原因:
cpp
不清空 → 析构 double free
清空 → delete nullptr 安全
八、现实类比
cpp
拷贝构造 = 复印整箱书
移动构造 = 把箱子搬走
拷贝赋值 = 先扔旧书再复印新书
移动赋值 = 先扔旧书再搬新箱子
九、终极锚点总结
cpp
拷贝构造 → 复制资源
移动构造 → 搬资源给新对象
拷贝赋值 → 复制资源给已有对象
移动赋值 → 清空自己再搬资源
移动语义不是复杂语法,
它是 "拷贝太贵"被逼出来的性能进化。
当你理解:
cpp
资源复制 vs 资源转移
移动构造和移动赋值就再也不会混了。