std::shared_ptr的引用计数操作是线程安全的,但shared_ptr对象本身及所指对象的访问需额外同步;多个线程并发修改同一shared_ptr变量必须加锁或使用c++20的std::atomic。

std::shared_ptr 本身不是完全线程安全的,但它的引用计数操作是线程安全的——关键在区分“指针对象”和“它指向的对象”。
shared_ptr 的引用计数增减是原子的
多个线程同时拷贝、赋值或析构不同的 shared_ptr 实例(哪怕它们共享同一对象),不会破坏引用计数。这是由标准保证的:控制块里的 shared_count 和 weak_count 都是 std::atomic<size_t></size_t>。
常见错误是误以为“引用计数安全”等于“整个使用过程安全”。比如:
- 线程 A 执行
ptr.reset(new int(42)),线程 B 同时执行ptr.use_count()→ ❌ 数据竞争,ptr本身被并发读写 - 线程 A 和 B 都执行
auto local = global_ptr;→ ✅ 安全,各自拷贝,只读global_ptr,引用计数原子递增
访问 shared_ptr 管理的对象必须自己同步
shared_ptr 不提供对所指对象的任何并发保护。即使引用计数没坏,对象内部状态也可能被撕裂。
典型崩溃场景:
- 两个线程同时执行
(*ptr)++→ 结果非预期(如期望 20000,实际 1375690) - 一个线程调用
ptr->process(),另一个线程正在析构该对象(因其他shared_ptr全部离开作用域)→ 未定义行为
安全做法:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 用
std::mutex保护对象的读写临界区 - 若只需读,且对象本身无内部状态变更,可不加锁(但需确保生命周期足够长)
- 考虑用
std::atomic<t></t>替代裸T*,如果操作足够简单(如整型计数)
多个线程修改同一个 shared_ptr 变量必须加锁或换 atomic
对同一个 shared_ptr 变量(如全局 g_ptr)做 reset()、operator= 或移动赋值,是典型的“多写一”数据竞争。
C++20 前唯一可靠方式是加互斥锁:
std::mutex g_ptr_mutex;
std::shared_ptr<int> g_ptr = std::make_shared<int>(0);
// 线程中修改
{
std::lock_guard<:mutex> lk(g_ptr_mutex);
g_ptr = std::make_shared<int>(42);
}
</int></:mutex></int></int>
C++20 起可用 std::atomic<:shared_ptr>></:shared_ptr>,但注意它只保证指针本身的原子读写,不改变所指对象的线程安全性。
容易被忽略的 weak_ptr::lock() 场景
当需要“先检查对象是否还活着,再安全访问”时,别直接解引用 shared_ptr。正确姿势是用 weak_ptr + lock():
-
weak_ptr::lock()是线程安全的,返回一个临时shared_ptr,若对象已销毁则为空 - 避免“检查后使用”间隙中对象被销毁(TOCTOU 问题)
- 尤其适合观察者模式、缓存、异步回调等生命周期不确定的场景
示例:
std::weak_ptr<myclass> observer;
// ...
if (auto sp = observer.lock()) { // 原子检查+提升
sp->do_something(); // 此时 sp 保证有效
} else {
// 对象已释放,跳过
}
</myclass>
真正棘手的地方从来不是“怎么让引用计数不出错”,而是“怎么让多个线程对同一块内存的读写不互相踩脚”。shared_ptr 只管前者,后者得你自己画线、上锁、或换数据结构。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










