定位与实现原理
-
功能 :管理共享所有权 的资源,兼具 "自动垃圾回收"(无需手动管理生命周期)和 "销毁可预测"(最后一个指向资源的
shared_ptr销毁时,资源确定销毁)的优势,比纯垃圾回收语言的资源管理更通用、销毁时机更可控。 -
实现基础 :引用计数(跟踪指向资源的
shared_ptr数量):- 拷贝 / 构造:递增引用计数;
- 析构 / 赋值指向其他对象:递减引用计数;计数归 0 时销毁资源;
- 移动更高效:移动
shared_ptr无需修改引用计数(源指针置空),比拷贝快。
-
性能开销 (对比原始指针 /
unique_ptr):- 大小:原始指针的 2 倍(存储 "指向资源的指针"+"指向引用计数的指针");
- 额外成本:引用计数需动态分配(
make_shared可优化)、引用计数修改是原子操作(线程安全但比非原子操作慢)。
自定义删除器
差异:删除器类型不是 shared_ptr 类型的一部分(unique_ptr 的删除器是类型一部分);
- 优势:不同删除器的同类型
shared_ptr(如shared_ptr<Widget>)可放入同一容器、相互赋值、传入同一函数; - 大小不变:删除器存储在 "控制块" 中,而非
shared_ptr对象本身,因此无论删除器是何类型,shared_ptr始终是 2 个指针大小。
控制块
-
定义:每个被
shared_ptr管理的资源对应一个控制块,除引用计数外,还存储自定义删除器 / 分配器等; -
创建规则(错用会导致未定义行为):
make_shared、从unique_ptr/ 原始指针构造:创建新控制块;- 从
shared_ptr/weak_ptr构造:复用已有控制块(不新建);
-
致命风险:从同一原始指针 创建多个
shared_ptr→ 生成多个控制块 → 资源被多次销毁;- 规避:① 直接传
new的结果给shared_ptr构造函数(而非原始指针变量);② 用已有shared_ptr拷贝构造新对象(复用控制块)。
- 规避:① 直接传
this 指针的安全使用(enable_shared_from_this)
- 问题:成员函数中直接传
this(原始指针)给shared_ptr→ 可能为同一对象创建新控制块(若外部已有指向该对象的shared_ptr); - 解决方案:让类继承
std::enable_shared_from_this<类名>(CRTP 模式),调用shared_from_this()替代this(复用已有控制块); - 前提:调用
shared_from_this()前,已有指向该对象的shared_ptr(否则行为未定义);最佳实践是类的构造函数私有化,通过工厂函数创建shared_ptr对象。
局限性与选型建议
- 不支持数组(C++17 前):API 为单个对象设计(无
operator[]),强行管理数组是糟糕设计,优先用std::array/vector; - 所有权不可变更:一旦资源由
shared_ptr管理,即使引用计数为 1,也无法转为unique_ptr; - 选型原则:能独占则优先用
unique_ptr(更高效),unique_ptr可无缝转为shared_ptr;仅确需共享所有权时,才用shared_ptr。
总结
std::shared_ptr是共享所有权资源的 "自动垃圾回收" 方案,核心靠引用计数 + 控制块实现,但有大小、原子操作等性能开销;- 自定义删除器不影响
shared_ptr类型,这是其比unique_ptr灵活的关键,但删除器存储在控制块中; - 避坑点:禁止从同一原始指针创建多个
shared_ptr,成员函数中用shared_from_this()替代直接传this; - 优先用
unique_ptr管理独占资源,仅共享时用shared_ptr。