std::shared_ptr自身线程安全,但所指对象访问需额外同步;weak_ptr需在lock成功后立即使用shared_ptr访问对象,避免use-after-free;make_shared优化内存分配与缓存局部性。

std::shared_ptr 在多线程中直接共享是否安全?
是安全的,但仅限于 std::shared_ptr 对象本身的拷贝/赋值/析构操作 —— 这些内部引用计数增减是原子的。真正不安全的是通过它访问所指向的对象(get() 或 *ptr)时没有额外同步。
常见错误现象:std::shared_ptr 本身没崩溃,但业务逻辑读写对象成员时出现数据竞争、野指针或断言失败。
- 引用计数原子操作 ≠ 所指对象线程安全
- 多个线程同时调用
ptr->do_something(),而do_something()非 const 且修改成员变量 → 必须加锁或用std::atomic等手段保护对象状态 - 若只读访问,且对象构造完成后不再修改(即“不可变对象”),则无需额外同步
如何避免 shared_ptr 析构时的跨线程 use-after-free?
核心在于:最后一个 std::shared_ptr 的析构可能发生在任意线程,而该析构会触发 delete(或自定义 deleter),如果此时其他线程还在用原始指针或 weak_ptr 锁定失败后继续访问,就崩了。
使用场景:工作线程持有一个 std::weak_ptr 定期尝试处理对象,主线程随时可能释放对象。
立即学习“C++免费学习笔记(深入)”;
- 永远不要从
std::weak_ptr::lock()得到std::shared_ptr后,再解引用裸指针(如ptr.get())并长期持有 - 所有对对象的访问,必须包裹在
if (auto sp = wp.lock()) { /* 用 sp->xxx */ }内,且不在 if 块外保留sp.get() - 避免在 deleter 中执行耗时或依赖其他线程资源的操作(比如发消息、等锁),否则可能阻塞析构线程并引发死锁
为什么不能用裸 new + 自己写引用计数替代 shared_ptr?
自己实现跨线程安全的引用计数,难点不在“计数”,而在“内存序”和“析构时机”的精确控制。标准库的 std::shared_ptr 在 C++11 起已明确定义了引用计数操作的 memory_order_acq_rel 行为,且与 std::weak_ptr 协同处理 ABA 和竞态析构问题。
容易踩的坑:
- 用
std::atomic_int做计数器,但忘记在递减后、delete 前插入memory_order_acquire栅栏 → 其他线程可能看到部分构造的对象状态 - 在 deleter 中 delete 对象,但没确保该 delete 不会触发另一个线程正在执行的虚函数调用(尤其涉及多态和动态加载模块时)
- 忽略
std::shared_ptr的控制块(control block)分配策略:默认堆分配,若高频创建销毁,会成为性能瓶颈;可重载new或用make_shared减少一次分配
std::make_shared 比 new + shared_ptr 构造快在哪?
关键差异在内存布局:std::make_shared 把控制块和对象数据一次性分配在同一块内存里;而 std::shared_ptr<t>(new T)</t> 是两次独立分配 —— 控制块一次、对象一次。
性能影响:
- 减少一次 malloc 调用,降低内存碎片和分配开销
- 提升缓存局部性:引用计数和对象数据更可能落在同一 cache line
- 但注意:若对象构造函数抛异常,
make_shared会同时释放控制块和未完成构造的对象;而手动 new + shared_ptr 构造时,若new T成功但 shared_ptr 构造失败(如控制块分配失败),会造成内存泄漏(极罕见,但需知道)
事情说清了就结束。最常被忽略的不是怎么写引用计数,而是忘了“引用计数安全”和“对象数据安全”是两层事,中间那条线划在哪,得看具体访问模式。










