现代 C++ 实战(05):智能指针(下)——模式与循环引用
上一篇我们掌握了 unique_ptr 和 shared_ptr 的基本用法。但在真实项目里,智能指针不只是替代 new/delete——循环引用、工厂函数、Pimpl、Observer 等场景会逼你思考「谁该拥有、谁该观察」。
这一篇是智能指针的下半场:weak_ptr 如何打破循环引用,以及几种经典设计模式如何用智能指针落地。
这是「现代 C++ 实战」系列的第 5 篇。对应 demo:
ref/cpp_demo/smart_pointers/第 05–09 节。建议先读 第 04 篇:智能指针(上)。
一、weak_ptr 与循环引用
shared_ptr 的引用计数是双向的:A 持有 B,B 也持有 A 时,计数永远不归零——内存泄漏。
1 | struct Node { |
| 指针类型 | 是否拥有对象 | 是否增加强引用计数 |
|---|---|---|
shared_ptr |
是 | 是 |
weak_ptr |
否(观察) | 否(只增加弱引用计数) |
使用 weak_ptr 前必须检查对象是否还活着:
1 | if (auto p = parent.lock()) { |
典型场景:树/图的父指针、Observer 列表、缓存中的非拥有引用。
二、工厂模式 + make_shared
工厂函数常返回 shared_ptr,配合私有构造函数控制创建入口:
1 | class Widget { |
好处:
- 构造逻辑集中,便于校验参数
- 调用方拿到明确的所有权语义
make_shared一次分配对象 + 控制块
三、代理模式:控制远程对象生命周期
代理持有真实对象的 shared_ptr,对外提供受限接口——真实对象在所有代理销毁后才释放:
1 | class ServiceProxy { |
多个 ServiceProxy 共享同一 Service,适合连接池、资源句柄包装等。
四、Pimpl:编译防火墙
Pointer to Implementation 把实现细节藏进 .cpp,头文件只暴露接口——减少编译依赖、加快增量构建。
1 | // widget.h |
unique_ptr 适合 Pimpl:实现类独占、不需共享;析构函数必须在 .cpp 中定义(Impl 完整类型可见)。
五、Observer:weak_ptr 作为观察者句柄
Subject 维护一组观察者,但不应拥有观察者生命周期:
1 | class Subject { |
若用 shared_ptr<Observer> 存储,Subject 与 Observer 可能互相 shared_ptr 导致泄漏;weak_ptr 只观察、不延长寿命。
六、Rule of Zero / Rule of Five
类成员全是智能指针(或标准容器)时,通常 Rule of Zero——五个特殊成员函数全部 = default 即可:
1 | class Document { |
若类自己管理裸指针或需要特殊语义,才手写 Rule of Five(析构、拷贝构造/赋值、移动构造/赋值)。
智能指针让大多数业务类直接 Rule of Zero,减少资源管理 bug。
七、选型指南
| 场景 | 推荐 |
|---|---|
| 单一所有者、工厂返回、Pimpl | unique_ptr |
| 多处共享、容器共享元素 | shared_ptr |
| 观察、打破循环引用、缓存键 | weak_ptr |
| 不拥有、生命周期由调用方保证 | T* / T& |
| 性能关键、明确不转移所有权 | T& 或 span |
决策顺序:能否 unique?→ 必须共享吗?→ 只是观察吗?→ 最后才考虑裸指针。
八、demo 导览:smart_pointers 05–09
ref/cpp_demo/smart_pointers/ 后半部分通常包含:
| 节 | 主题 |
|---|---|
| 05 | weak_ptr 与循环引用演示 |
| 06 | 工厂 + 私有构造 |
| 07 | 代理模式 |
| 08 | Pimpl 编译防火墙 |
| 09 | Observer + weak_ptr |
1 | cd ref/cpp_demo/smart_pointers |
建议对照源码里的构造/析构日志,观察引用计数变化与对象销毁时机。
九、小结
| 概念 | 要点 |
|---|---|
| 循环引用 | 双向 shared_ptr 泄漏 → 一侧改 weak_ptr |
| 工厂 | make_shared + 私有构造,统一创建入口 |
| Pimpl | unique_ptr<Impl> 隐藏实现,析构放 .cpp |
| Observer | weak_ptr 观察,.lock() 使用前检查 |
| 选型 | unique 默认,shared 真共享,weak 只观察 |
现代 C++ 实战系列第 5 篇完。下一篇进入 Lambda 与类型推导——现代 C++ 的瑞士军刀。
系列导航
| 篇号 | 标题 | 状态 |
|---|---|---|
| 04 | 智能指针(上) | ✅ |
| 05 | 智能指针(下):模式与循环引用(本篇) | ✅ |
| 06 | Lambda 与类型推导 | 下一篇 |
完整大纲见工作区 docs/CPP_SERIES_OUTLINE.md。










