c++oding="utf-8" ?>
不能直接继承std::shared_ptr管理的类,因为其引用计数由外部控制块托管,与对象内存不连续;侵入式引用计数需将计数器嵌入对象内部并统一继承基类(析构函数须为virtual),且禁止与std::shared_ptr混用,否则导致双重释放。

为什么不能直接继承 std::shared_ptr 管理的类?
因为 std::shared_ptr 的引用计数是外部托管的——它在堆上单独分配一个控制块,和对象本体内存不连续。一旦对象本身需要感知引用计数(比如在析构前做资源预释放、参与循环引用检测、或与 C API 交互),这种分离就成瓶颈。
侵入式引用计数把计数器直接嵌进对象内部,由对象自己维护 m_ref_count,add_ref() / release() 变成成员函数调用。这样做的前提是:你得让所有想被智能指针管理的类,统一继承一个基类。
- 继承基类后,对象大小增加
sizeof(int)(通常) - 所有派生类必须确保析构函数为
virtual,否则release()中的delete this会未定义行为 - 不能用
std::make_shared构造侵入式对象——它会绕过基类构造逻辑,且控制块与对象分离,失去“侵入”意义
intrusive_ptr 的 add_ref 和 release 必须是自由函数
Boost 和 libc++ 的 intrusive_ptr 不依赖基类接口,而是靠 ADL(Argument-Dependent Lookup)查找同名自由函数:add_ref(T*) 和 release(T*)。这意味着你可以不继承基类,只要为类型提供这两个函数即可。
但如果你选择基类方案(更直观、易统一),仍需将它们声明为非成员函数,并在基类所在命名空间中定义,否则 intrusive_ptr 找不到:
namespace mylib {
struct intrusive_base {
mutable std::atomic_int m_ref_count{0};
intrusive_base() = default;
intrusive_base(const intrusive_base&) = delete;
intrusive_base& operator=(const intrusive_base&) = delete;
};
<p>inline void add_ref(intrusive_base const* p) noexcept {
p->m_ref_count.fetch_add(1, std::memory_order_relaxed);
}</p><p>inline void release(intrusive_base const<em> p) noexcept {
if (p->m_ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete const_cast<intrusive_base>>(p);
}
}
}</intrusive_base></em></p>
- 函数签名必须严格匹配:
void add_ref(T const*),void release(T const*) -
release中的const_cast是必要的——因为intrusive_ptr持有const T*,但析构需要非常量访问权 - 原子操作用
memory_order_acq_rel足够;过度使用seq_cst会拖慢性能
手动管理 intrusive_ptr 生命周期时,拷贝/移动语义要小心
intrusive_ptr 默认行为是:拷贝时调用 add_ref,移动后原指针置空,析构时调用 release。这看似安全,但隐患藏在裸指针流转中:
- 从
intrusive_ptr获取裸指针(如.get())后,若把它传给另一个intrusive_ptr构造函数,会重复add_ref,但没人保证该裸指针“原本就归intrusive_ptr管” - 跨线程传递裸指针再构造
intrusive_ptr,可能引发竞态:一个线程刚release完,另一个线程正用get()结果调add_ref - 避免裸指针逃逸:不要把
get()结果存为普通T*成员变量,尤其在多线程对象里
正确做法是:始终用 intrusive_ptr 持有,需要共享时拷贝它;若必须传裸指针给 C 接口,确保 C 侧不长期持有或自行增减引用——那已超出 C++ RAII 边界。
和 std::shared_ptr 混用会导致双重释放
这是最危险也最容易被忽略的点:同一个对象,既被 intrusive_ptr 管理,又被 std::shared_ptr 包裹(例如误用 std::shared_ptr),就会出现两个独立的引用计数系统。
结果是:当任一智能指针析构到 0,它都会执行 delete this,第二次就是野指针操作。
- 绝对禁止对侵入式对象调用
std::shared_ptr或(raw_ptr) std::shared_ptr::reset(raw_ptr) - 如果已有
std::shared_ptr生态,想渐进迁移到侵入式,必须确保所有持有者同步切换,中间不能存在混合生命周期 - 调试时可加 assert:在
release中检查m_ref_count.load() == 0是否成立,提前暴露计数错乱
侵入式引用计数真正起效的前提,是整个模块对对象所有权的约定一致——它不提供“自动兜底”,只提供更紧耦合、更低开销的控制权移交机制。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











