shared_ptr的引用计数存于控制块中,非对象本身;拷贝/赋值使shared_count原子加1,reset或析构时原子减1,减至0时释放对象,weak_count为0时才释放控制块。

shared_ptr 的引用计数在哪儿存、怎么变
引用计数不存于 shared_ptr 对象本身,而是存在它背后的「控制块(control block)」里。每个 shared_ptr 实际包含两个指针:get() 返回的裸指针(指向对象),和一个隐式指向控制块的指针。控制块里有至少两个原子整数:shared_count(当前有多少个 shared_ptr 指向该对象)和 weak_count(有多少个 weak_ptr 在观察它)。
常见错误现象:用 new 构造多个 shared_ptr 指向同一裸指针,比如:
int* raw = new int(42); shared_ptr<int> p1(raw); shared_ptr<int> p2(raw); // ❌ 危险!各自建控制块,引用计数互不感知 </int></int>
结果是两次析构、double free —— 因为 p1 和 p2 各自维护独立的控制块,各自计数归零时都会调用 delete raw。
- 拷贝构造或赋值(
p2 = p1)→shared_count原子加 1 -
reset()、离开作用域、或被赋值为nullptr→shared_count原子减 1 -
shared_count减到 0 时,控制块才调用删除器释放对象;此时若weak_count也为 0,控制块自身也被释放
make_shared 为什么能避免两次分配
make_shared 不是语法糖,它把「对象内存」和「控制块内存」一次性分配在同一块连续内存中。而 shared_ptr<t>(new T)</t> 是两次分配:一次给 T 对象,一次给控制块 —— 多一次系统调用、更差的缓存局部性、还可能因分配失败导致异常安全问题(比如控制块分配成功但对象构造抛异常,控制块就泄露了)。
使用场景:只要不是需要自定义分配器或非默认构造,一律优先用 make_shared。例如:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
auto p = make_shared<vector>>(10, "hello"); // ✅ 一次分配,vector 被完美转发构造 // 而不是: auto p = shared_ptr<vector>>(new vector<string>(10, "hello")); // ❌ 两次分配,且无异常安全保证 </string></vector></vector>
- C++17 起,
make_shared支持数组类型:make_shared<int>(100)</int>,内部自动用delete[] - 如果必须用
new(比如要传自定义删除器),务必确保所有shared_ptr都来自同一源头,绝不要对同一裸指针多次构造
循环引用时计数为啥卡住不归零
循环引用不是计数“坏了”,而是逻辑上形成了闭环:A 的 shared_ptr 持有 B,B 的 shared_ptr 又持有 A。只要任一端还活着,对方的 shared_count 就永远 ≥1,导致双方都无法释放。
典型场景:树节点、观察者模式、父子对象关系。例如:
struct Node {
shared_ptr<node> parent;
shared_ptr<node> child;
};
auto root = make_shared<node>();
auto leaf = make_shared<node>();
root->child = leaf;
leaf->parent = root; // ❌ 循环:root 和 leaf 互相延长对方生命周期
</node></node></node></node>
- 解决办法是打破强引用链:把
parent改成weak_ptr<node></node> -
weak_ptr不增加shared_count,只增weak_count;调用lock()才临时转成shared_ptr(并检查对象是否还存活) - 注意:
weak_ptr::lock()不是线程锁,是“尝试升级为共享指针”的操作
多线程下计数操作是安全的,但对象访问不是
shared_count 和 weak_count 的增减是原子操作,所以多个线程同时拷贝、赋值、析构同一个 shared_ptr 是安全的 —— 不会崩、不会漏减、不会错加。
但这是**仅限于智能指针本身的操作**。对象内部数据的读写仍需额外同步:
shared_ptr<vector>> data = make_shared<vector>>(); // 线程 A: data->push_back(42); // ❌ data 是线程安全的,但 vector::push_back 不是 // 线程 B: cout size(); // ❌ 同样不安全 </vector></vector>
- 引用计数原子性 ≠ 对象线程安全
- 如果多个线程通过不同
shared_ptr访问同一对象,必须自己加锁(如mutex)或用无锁结构 - 尤其注意:
get()返回的裸指针不能跨线程长期持有,因为无法保证对象不被其他线程的shared_ptr析构掉
最易被忽略的一点:控制块的生命周期由 shared_count 和 weak_count 共同决定。哪怕所有 shared_ptr 都没了,只要还有 weak_ptr 存活,控制块就不能释放 —— 这是为了让 weak_ptr::lock() 能安全判断对象是否还在。别以为 use_count() == 0 就万事大吉,得看 weak_count。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










