std::shared_ptr原子引用计数拖慢性能是因为即使单线程下也无法省略原子指令,导致内存屏障、优化受限及缓存一致性开销;实测tight loop中性能比裸指针低3–5倍。

为什么 std::shared_ptr 的原子引用计数会拖慢性能?
std::shared_ptr 默认使用原子操作(如 std::atomic_fetch_add)管理引用计数,确保多线程环境下安全。但即使在单线程场景下,编译器也无法省略这些原子指令——它们会强制内存屏障、禁用部分优化,并可能触发锁总线或缓存一致性协议开销。实测中,在 tight loop 里频繁拷贝/析构 std::shared_ptr,性能可能比裸指针低 3–5 倍。
如何关闭原子性:用 std::make_shared 配合自定义删除器?
不能直接“关掉”原子性——std::shared_ptr 标准规定其控制块的引用计数必须是线程安全的。但你可以绕过默认控制块,用 std::shared_ptr 的别名构造 + 自定义删除器来避免原子操作:
struct NonAtomicDeleter {
void operator()(int* p) const noexcept {
delete p;
}
};
// 构造时不经过标准控制块,引用计数不启用原子操作
auto ptr = std::shared_ptr(new int(42), NonAtomicDeleter{});
注意:std::make_shared 无法用于此场景,因为它总会分配带原子计数的控制块;必须用原始指针 + 自定义删除器构造。
- 该方式仅适用于明确单线程上下文,且你完全掌控所有
shared_ptr实例的生命周期 - 不能与通过
std::make_shared创建的同对象shared_ptr混用,否则控制块不一致,导致未定义行为 - 拷贝该
shared_ptr仍会调用原子操作——因为shared_ptr的拷贝赋值/构造函数内部仍会访问控制块的引用计数
更实际的替代方案:std::unique_ptr 或裸指针 + 明确所有权
若不需要共享语义,std::shared_ptr 本身就是误用。性能问题只是表象,根源在于设计阶段没厘清所有权:
立即学习“C++免费学习笔记(深入)”;
- 用
std::unique_ptr替代:零运行时开销,移动语义清晰,且能配合std::move和std::make_unique - 若需跨函数传递只读访问,优先传
const T&或T*,而非std::shared_ptr - 若确实要共享,但只在单线程内传播(如配置树、AST 节点),可封装一个非原子版
shared_ptr(如 Boost 的boost::local_shared_ptr),它用普通整型计数,不依赖原子指令
何时必须接受原子开销?
只要出现以下任一情况,就无法规避原子引用计数:
- 多个线程同时拷贝、赋值或销毁指向同一对象的
std::shared_ptr - 使用
std::weak_ptr—— 它依赖同一控制块中的原子弱引用计数 - 通过
std::shared_ptr::owner_before或比较操作做排序,因为实现依赖控制块地址和原子状态 - 将
std::shared_ptr存入std::vector并频繁 resize —— 每次元素移动都触发引用计数增减
真正难处理的不是原子操作本身,而是人们把 std::shared_ptr 当作“不用动脑的所有权解决方案”,结果在 hot path 上反复构造/拷贝,却没意识到大部分时候只需要移动或引用。











