Lazy loaded image
3. 生命周期与资源管理
Words 1929Read Time 5 min
2026-9-4
🧠
核心结论
C++ 资源管理的关键,不是“记得手动释放”,而是让资源的生命周期绑定到对象:对象离开作用域,析构函数自动清理。优先使用值语义、标准容器和智能指针;裸 new/delete 应是少数情况。
  • 生命周期错误的根源通常不是“指针语法”,而是所有权不清
  • RAII 用析构函数统一清理资源,覆盖正常返回和异常路径。
  • 优先遵循 Rule of 0;不要一上来手写“五件套”给自己加班。
  • unique_ptr 是默认选择,shared_ptr 不是“更高级的 unique_ptr”。
  • auto_ptr 的复制会转移所有权,C++11 已弃用、C++17 已移除;新代码不要使用。

3.1 存储区域与对象生命周期

存储区域不等于存储期

  • 存储区域回答“对象通常放在哪里”:栈、堆、静态存储区。
  • 存储期回答“对象活多久”:自动、动态、静态、线程存储期。
类别
典型对象
创建与销毁
主要风险
栈(自动存储)
局部变量
进入作用域创建,离开时自动析构
返回局部变量地址导致悬空
堆(动态存储)
new 创建的对象
由所有者负责释放
泄漏、重复释放、悬空指针
静态存储区
全局变量、static 对象
程序开始至结束
跨文件初始化顺序问题
notion image

构造与析构顺序

同一作用域内,通常按定义顺序构造、逆序析构;成员按其在类中的声明顺序构造,而不是初始化列表的书写顺序。

临时对象、悬空指针与内存泄漏

上面的 Trace 示例只说明具名局部对象的构造与析构顺序,不能直接说明下面三类问题,因此单独讨论。
悬空指针:对象已经销毁,指针却仍保存原地址;此时解引用属于未定义行为。
内存泄漏:动态分配的对象仍存在,但程序丢失了用于释放它的地址。
📌
三者不是一回事:临时对象描述生命周期规则,悬空指针是对象已死、地址还在,内存泄漏是对象还在、地址没了

3.2 动态内存

方式
分配/释放
构造/析构
失败行为
C++
new/delete
会调用
new 默认抛出 std::bad_alloc
C
malloc/free
不会调用
malloc 返回 nullptr
⚠️
不要混用 new/freemalloc/delete,也不要用 delete 释放 new[] 的结果。这些都属于未定义行为。

3.3 RAII:把清理动作交给析构函数

RAII(资源获取即初始化):对象构造时取得资源,对象析构时释放资源。资源不仅是内存,也包括文件、锁、套接字和线程句柄。
上例中的 out 是一个局部 std::ofstream 对象:构造时尝试打开并持有文件资源,离开作用域时析构函数自动关闭文件。因此,即使函数通过 return 提前返回,或后续代码抛出异常并发生栈展开,也不需要为每条退出路径重复调用 close()

3.4 拷贝与移动

notion image
数字表示类通常需要关注或自定义的特殊成员函数数量。这些是 C++ 社区设计准则,并非语言强制规则。
  • Rule of 0:资源交给标准类型管理,业务类无需手写析构、拷贝和移动。例如用 std::vector<int> 保存数组,离开作用域时自动释放。
  • Rule of 3:C++98/03 中,若类需要自定义析构函数,通常还要自定义拷贝构造和拷贝赋值。例如持有 char* 的字符串类必须深拷贝,否则可能重复释放。
  • Rule of 5:C++11 后,在上述三个函数之外,再增加移动构造和移动赋值。例如 Buffer 移动时可直接转移内存所有权,避免复制整块数据。
工程上优先遵循 Rule of 0;只有类必须直接管理底层资源时,才考虑 Rule of 5。
这个 Buffer 表达的是独占所有权
  • unique_ptr 在对象析构时自动释放数组,因此不需要手写析构函数。
  • 删除拷贝构造和拷贝赋值,防止两个 Buffer 同时拥有同一块内存
  • 默认移动构造和移动赋值会把 data_ 的所有权转给新对象,避免复制整块数组;noexcept 也便于标准容器安全地使用移动操作。
需要注意:默认移动后,源对象的 data_ 会变为空,但标量成员 size_ 仍保留原值。若类要求“空指针必须对应长度 0”,就应自定义移动操作并将源对象的 size_ 置为 0。

3.5 智能指针:先判断所有权,再选类型

类型
所有权
典型场景
注意点
unique_ptr
独占
对象只有一个明确所有者
不可复制,可用 std::move 转移
shared_ptr
共享
多个对象确实共同拥有资源
引用计数有开销,可能循环引用
weak_ptr
观察,不拥有
缓存、观察者、打破循环引用
使用前调用 lock()

unique_ptr:独占所有权

notion image

shared_ptr + weak_ptr:共享与观察

notion image
📌
选择顺序
  1. 能直接存对象,就不要存指针。
  1. 需要动态对象时,默认选择 unique_ptr
  1. 只有所有权确实共享时,才使用 shared_ptr
  1. 只访问、不负责保活时,使用引用、裸指针或 weak_ptr 表达“非拥有关系”。
 
上一篇
C++ String 常用操作
下一篇
嵌入式工程师习题集

Comments
Loading...