对象生命周期、特殊成员函数与移动语义
对象生命周期、特殊成员函数与移动语义
时间:2026/04/09
关键词:生命周期、RAII、拷贝构造、拷贝赋值、移动构造、移动赋值、Rule of Zero/Five、
std::move
核心目标:搞清楚一个对象从创建到销毁会经历什么,以及类该如何正确管理资源。
1. 对象生命周期是什么
一个对象通常会经历:
- 构造
- 使用
- 析构
最重要的实践原则是:
- 对象一旦构造完成,就应该处于“可用且满足类不变量”的状态
- 对象一旦析构完成,就不该再被访问
局部对象在离开作用域时析构:
1 | void f() { |
动态对象则由拥有者负责释放:
1 | auto p = std::make_unique<int>(42); |
2. RAII 是生命周期管理的核心
RAII 的意思是:
- 构造时获取资源
- 析构时释放资源
典型资源包括:
- 动态内存
- 文件句柄
- socket
- 锁
- 线程句柄
RAII 的价值不是“语法优雅”,而是:
- 不容易忘记释放
- 异常发生时也能自动清理
3. 六个特殊成员函数
一个类最重要的 6 个函数是:
- 默认构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
它们决定了对象如何:
- 创建
- 复制
- 转移资源
- 销毁
4. 拷贝构造 vs 拷贝赋值
4.1 拷贝构造
用一个对象去初始化另一个“新对象”:
1 | T b(a); |
4.2 拷贝赋值
把一个已经存在的对象覆盖成另一个对象的状态:
1 | T b; |
核心差异:
- 拷贝构造是“从无到有”
- 拷贝赋值是“已有对象被覆盖”
后者通常还要考虑:
- 旧资源释放
- 自赋值
- 异常安全
5. 为什么移动语义很重要
如果一个类持有资源,单纯拷贝代价可能很大。
例如:
- 动态数组
- 大字符串
- 文件句柄包装对象
移动语义的核心思想是:
- 不复制资源内容
- 直接转移所有权或资源句柄
这就需要:
- 移动构造
- 移动赋值
6. std::move 到底在做什么
std::move 本身不移动资源。
它只是把一个表达式转换成右值形式,让后续重载决议优先匹配移动版本。
1 | std::string s = "hello"; |
真正移动的是:
std::string的移动构造函数
不是 std::move 本身。
7. 一个最小资源类示例
1 |
|
这个例子体现了:
- 深拷贝
- 资源转移
- 移动后源对象置空
8. Rule of Three / Five / Zero
8.1 Rule of Three
如果类手写了下面之一,通常就要认真考虑另外两个:
- 析构
- 拷贝构造
- 拷贝赋值
因为这通常意味着类在管理资源。
8.2 Rule of Five
C++11 以后再加上:
- 移动构造
- 移动赋值
如果类显式管理资源,通常要一起考虑这五个。
8.3 Rule of Zero
现代 C++ 更推荐:
- 尽量不要自己手写资源管理
- 把资源交给现成 RAII 类型
例如:
std::vectorstd::stringstd::unique_ptr
这样很多特殊成员函数甚至可以完全默认生成。
9. 默认生成和 = default / = delete
9.1 = default
显式告诉编译器:
- 用默认生成版本
1 | MyType() = default; |
9.2 = delete
显式禁止某种操作:
1 | MyType(const MyType&) = delete; |
这在:
- 独占资源类型
- 锁对象
- 文件句柄包装类
里很常见。
10. 拷贝省略与返回值优化
现代 C++ 里:
1 | Buffer make_buffer() { |
很多情况下不会真的发生拷贝,甚至连移动都可能被省掉。
这就是:
- RVO
- NRVO
- guaranteed copy elision
所以写代码时不要过度手工干预,先让编译器优化。
11. 常见坑
11.1 移动后还把源对象当原值使用
移动后的对象通常只保证:
- 仍然有效
- 可以析构或重新赋值
但不保证保留原内容。
11.2 手写资源类却只写析构,不写拷贝/移动
这很容易造成:
- 双重释放
- 浅拷贝问题
11.3 拷贝赋值没处理自赋值和异常安全
尤其是手动 delete 再 new 的写法,容易把对象弄到半残状态。
11.4 本来可以 Rule of Zero,却硬写五个函数
不必要的手写资源管理会增加 bug 面积。
12. 一页总结
这篇最重要的是记住三件事:
- 生命周期就是“构造到析构”的可控过程
- 管资源的类必须认真处理拷贝和移动
- 最好的实践通常不是手写五件套,而是尽量 Rule of Zero
如果只记一个工程结论:
能把资源交给现成 RAII 类型,就不要自己手写裸资源生命周期。
13. 建议继续补充的相关主题
- 智能指针与所有权
- 完美转发与引用折叠
- 异常安全保证
noexcept move与容器优化