intrusive_ptr比shared_ptr少一次内存分配,因其引用计数嵌入对象内部,无需额外分配控制块;但要求类型显式支持侵入式计数,且add_ref/release_ref必须与类型同命名空间以支持adl查找。

intrusive_ptr 为什么比 shared_ptr 少一次内存分配
因为 intrusive_ptr 不自己管理引用计数,而是要求对象内部提供 add_ref() 和 release_ref() 方法,并把计数器(通常是 int 或 std::atomic<int></int>)作为成员变量嵌入对象本身。这样就省掉了 shared_ptr 额外分配一块堆内存来存控制块的开销。
但代价是:对象必须预先设计为支持侵入式引用计数,不能对任意类型透明使用。
常见错误现象:intrusive_ptr 构造后调用 use_count() 返回 0,或析构时崩溃——多半是没正确实现 intrusive_ptr_add_ref/intrusive_ptr_release 特化,或计数器未初始化为 1(首次构造时需手动设为 1)。
实操建议:
- 继承一个基类(如
ref_counted),含std::atomic<int> ref_count_</int>,构造函数中初始化为 0;首次交由intrusive_ptr管理前,手动调用add_ref() - 或更稳妥:不依赖构造函数初始化,而是在创建对象后、构造
intrusive_ptr前,显式调用add_ref()一次(等价于“初始引用”) - 必须提供两个非成员函数特化:
intrusive_ptr_add_ref(T*)和intrusive_ptr_release(T*),它们会被intrusive_ptr内部调用
如何正确定义 intrusive_ptr_add_ref / intrusive_ptr_release
这两个函数不是类成员,也不在命名空间内随便放——它们必须与被管理类型的定义在同一个命名空间中,否则 ADL(参数依赖查找)会找不到,导致编译失败或链接时报错 undefined reference to intrusive_ptr_add_ref。
典型写法(假设类型叫 MyClass):
class MyClass {
public:
std::atomic<int> ref_count_{0};
void add_ref() { ref_count_.fetch_add(1, std::memory_order_relaxed); }
void release_ref() {
if (ref_count_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
}
}
};
<p>// 必须和 MyClass 在同一命名空间!
void intrusive_ptr_add_ref(MyClass<em> p) { p->add_ref(); }
void intrusive_ptr_release(MyClass</em> p) { p->release_ref(); }
</p></int>
注意点:
- 不要加
namespace boost { ... }包裹——那是 Boost 版本的老习惯,现代用法(尤其是 C++20 后自定义智能指针)依赖 ADL,放在同命名空间才有效 - 计数器操作推荐用
std::memory_order_acq_rel(fetch_sub)和relaxed(fetch_add),兼顾性能与正确性 - 如果
MyClass是模板类,这两个函数也得是模板特化,不能只写一个泛型版本
intrusive_ptr 构造和赋值时的引用计数陷阱
直接用原始指针构造 intrusive_ptr 不会自动调用 add_ref() —— 它假定你传入的指针已经持有“一个引用”。所以如果你刚 new 出对象,又没手动 add_ref(),构造后引用计数仍是 0,第一次 reset() 或离开作用域就会释放它。
正确做法只有两种:
- 先
new对象 → 手动add_ref()→ 再构造intrusive_ptr(最直白) - 封装工厂函数:
intrusive_ptr<myclass> make_my_class() { auto p = new MyClass; p->add_ref(); return {p}; }</myclass>
容易踩的坑:
- 用
intrusive_ptr<t>(new T)</t>直接构造:99% 情况下触发 double-delete 或 use-after-free - 把同一个原始指针反复构造多个
intrusive_ptr:每个都以为自己拥有唯一引用,析构时各自release_ref(),计数器变负,delete 多次 - 混用
intrusive_ptr和裸指针管理:裸指针 delete 后,intrusive_ptr还在 hold,后续访问就是悬垂指针
和 shared_ptr 混用是否可行
完全不可行。两者引用计数机制隔离:shared_ptr 的控制块里存的是它自己的计数,intrusive_ptr 完全不感知;反之亦然。强行把同一对象既交给 shared_ptr 又交给 intrusive_ptr,等于两个独立计数器在删同一个对象,必然 crash。
如果你需要过渡或兼容,只能选一边:
- 已有大量
shared_ptr使用?别改,intrusive_ptr的收益(单次分配节省)在多数场景不值得重构成本 - 对象生命周期极短、每秒创建百万级、且内存分配是瓶颈?这时侵入式才有意义,但必须从头设计,禁用所有裸指针和 shared_ptr 接口
- 想“部分侵入”?不行。要么整个模块统一用
intrusive_ptr+ 自定义计数,要么全用shared_ptr
真正容易被忽略的一点:intrusive_ptr 的线程安全性只取决于你写的 add_ref/release_ref 是否原子——它自己不做额外同步。如果多个线程并发构造/拷贝/析构同一个 intrusive_ptr 实例,而你的计数器不是 std::atomic,行为未定义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











