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