跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

C++实现轻量级侵入式引用计数基类 _ 智能指针生命周期管理【源码】

多数场景不该手写RefCountedBase,因其带来内存布局污染、线程安全陷阱和生命周期割裂三类成本;真需侵入式计数时,必须用std::atomic、禁用拷贝、正确实现ref/unref及protected虚析构。 直接用
std::shared_ptr
就行,别自己写侵入式引用计数基类 —— 除非你明确需要零分配、确定性析构、或与 C API/裸指针生态深度互操作。 为什么多数场景不该手写
RefCountedBase
自己实现侵入式引用计数(比如带
ref()
/
unref()
的基类)看似轻量,实际隐藏三类成本: 内存布局污染:每个派生类对象多占 4 或 8 字节(
m_refCount
),且无法被编译器优化掉 线程安全陷阱:
std::atomic
的读-改-写开销比
std::shared_ptr
的控制块原子操作更难对齐,容易漏掉
memory_order
约束 生命周期割裂:
new T
+
ref()
和
unref()
手动配对,极易出现悬空或泄漏;而
std::shared_ptr(new T)
或
std::make_shared()
天然绑定构造与计数 真要写,必须用
std::atomic
且禁止拷贝构造 常见错误是把引用计数成员声明为普通
int
,或在基类里提供默认拷贝构造函数 —— 这会导致两个指针同时指向同一对象却各自维护独立计数,
unref()
后立即释放,另一方变野指针。 正确做法: 立即学习 “ C++免费学习笔记(深入) ”;
m_refCount
必须是
std::atomic
,初始化为 1(对象诞生即被持有) 基类禁用拷贝:
RefCountedBase(const RefCountedBase&) = delete;
,移动也禁用(侵入式计数不支持转移语义)
unref()
必须用
fetch_sub(1, std::memory_order_acq_rel)
,检查返回值是否为 1 再
delete this
;不能用
--m_refCount == 0
析构函数必须是
protected virtual
,防止用户直接
delete p
示例关键片段:
class RefCountedBase { protected: std::atomic m_refCount{1}; virtual ~RefCountedBase() = default; RefCountedBase(const RefCountedBase&) = delete; void ref() { m_refCount.fetch_add(1, std::memory_order_relaxed); } void unref() { if (m_refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete this; } } };
和
std::shared_ptr
混用时,
enable_shared_from_this
是唯一安全桥接方式 如果已有侵入式基类,又想临时转成
std::shared_ptr
(比如调第三方库),绝不能写
std::shared_ptr(raw_ptr)
—— 这会新建控制块,导致双重释放。 C知道 CSDN推出的一款AI技术问答工具 下载 正确路径只有两条: 让类继承
std::enable_shared_from_this
,并在构造时确保对象一定由
std::shared_ptr
管理(即只通过
std::make_shared
创建) 若必须从裸指针出发,且该指针确属某
std::shared_ptr
管理,则需原始控制块信息 —— 实际不可行,等价于放弃安全 换句话说:侵入式基类和
std::shared_ptr
属于两种内存管理范式,硬桥接等于主动放弃 RAII 保障。 真正轻量且现代的替代方案:用
std::shared_ptr
+ 自定义删除器 如果你的目标是“零额外内存分配”或“精确控制释放时机”,与其重写基类,不如定制
std::shared_ptr
的删除行为: 用静态池或 arena 分配对象,再传给
std::shared_ptr
,删除器调用
pool.free()
而非
delete
删除器可触发回调、日志、甚至延迟释放(如等待 GPU 完成) 所有接口保持标准,兼容 STL 算法、
std::weak_ptr
、线程安全,且无基类侵入 例如:
struct ArenaDeleter { Arena* arena; void operator()(MyType* p) const { arena->free(p); } }; auto ptr = std::shared_ptr(arena->alloc(), ArenaDeleter{arena});
侵入式引用计数不是“更轻”,而是“换一种重法”——它把复杂性从堆分配挪到了对象布局、线程同步和生命周期契约上。除非你在写图形驱动、嵌入式中间件或对接 legacy C 库,否则优先让
std::shared_ptr
做它该做的事。

相关文章