std::shared_ptr通过独立控制块管理引用计数,强引用归零时析构对象并释放控制块;make_shared更高效且防异常泄漏,但需避免裸指针重复构造、循环引用及this误用。

std::shared_ptr 是怎么管理引用计数的
它不是靠指针本身存计数,而是额外分配一块控制块(control block),里面放引用计数、弱引用计数、析构器、分配器等信息。每次拷贝 std::shared_ptr,控制块里的强引用计数就 +1;每次离开作用域或被赋值为 nullptr,就 -1;减到 0 时,自动 delete 所管理的对象,并释放控制块。
- 控制块和对象可以分开分配(默认行为),也可能合并在一次
new中(用std::make_shared时)——后者更高效,但要注意:如果对象构造抛异常,控制块仍会泄漏(C++17 起已修复) - 引用计数操作是原子的,所以
std::shared_ptr可在多线程中安全拷贝/赋值,但指向的 *对象本身不自动线程安全* - 不要混用裸指针初始化:
int* p = new int(42); auto sp1 = std::shared_ptr——这会创建两个独立控制块,双重 delete(p); auto sp2 = std::shared_ptr (p);
什么时候必须用 std::make_shared 而不是 new + 构造函数
绝大多数情况都该用 std::make_shared:它把对象和控制块一次分配,避免两次内存申请,也杜绝了因异常导致的资源泄漏风险(比如构造函数抛异常时,裸 new 分配的内存可能没被接管)。
- 例外:需要自定义删除器且不能默认构造(
std::make_shared不接受删除器参数),此时只能用std::shared_ptr(new T, my_deleter) - 注意:不能对数组用
std::make_shared——C++17 前不支持,即使支持,() std::shared_ptr也不带[]删除语义,应改用std::shared_ptr+ 自定义删除器 -
std::make_shared完转发参数给 T 的构造函数,完美支持 explicit 构造、初始化列表、移动语义
std::shared_ptr 和 std::weak_ptr 配合破循环引用的关键点
循环引用不是“两个 shared_ptr 互相持有”,而是“两个对象通过 shared_ptr 成员彼此持有对方”,导致引用计数永远不归零。解决方式是其中一方改用 std::weak_ptr ——它不增加强引用计数,只在需要时调用 lock() 升级为 shared_ptr。
-
std::weak_ptr的lock()返回std::shared_ptr,若原对象已被销毁,则返回空shared_ptr;别直接解引用weak_ptr,它没有operator-> - 判断是否过期:用
wptr.expired()比wptr.lock() != nullptr稍快,但语义一致;真正使用前仍建议先lock()再判空,避免竞态 - 注意:
std::weak_ptr本身也参与控制块的弱引用计数,控制块生命周期由强+弱引用共同决定;只有强引用为 0 且弱引用也为 0 时,控制块才释放
常见崩溃和误用场景(附错误信息提示)
很多 segfault 或 double-free 其实源于对控制块生命周期或所有权转移的理解偏差。
立即学习“C++免费学习笔记(深入)”;
-
崩溃点:对已 reset 的
shared_ptr调用get()后解引用 →nullptr解引用,报Segmentation fault (core dumped) -
崩溃点:用同一个裸指针重复初始化多个
shared_ptr→ 控制块独立,最终 double free,报double free or corruption -
陷阱:从
this构造shared_ptr(如std::shared_ptr)——除非类继承自(this) std::enable_shared_from_this并用shared_from_this(),否则极大概率重复管理 -
性能坑:频繁调用
use_count()——它是原子读,但无必要;仅用于调试,禁止用于逻辑分支(如if (sp.use_count() == 1))
控制块的存在形式、跨线程安全性边界、以及 weak_ptr 的“临时升级”机制,是理解 std::shared_ptr 行为差异的核心;实际写代码时,优先用 make_shared,禁用裸指针构造,循环引用必须显式拆解——这些不是最佳实践建议,而是避免 undefined behavior 的硬性约束。










