std::shared_ptr通过控制块维护共享计数和弱引用计数,仅当二者均为0时才释放资源;循环引用时因彼此强引用致共享计数永不归零,对象无法析构,导致内存持续增长。

std::shared_ptr 的引用计数机制是怎么工作的
它靠内部一个控制块(control block)维护两个计数:指向对象的 shared_ptr 数量,和指向该控制块的 weak_ptr 数量。只有当这两个计数都归零,资源才会被释放。
关键点在于:只要还有任意一个 shared_ptr 持有对象,对象就不会析构;而 weak_ptr 不影响对象生命周期,只影响控制块本身是否销毁。
- 每次拷贝
shared_ptr,共享计数 +1;析构或赋值覆盖时 -1 -
weak_ptr构造/拷贝只增控制块引用计数,不增共享计数 - 调用
weak_ptr::lock()返回临时shared_ptr,会短暂增加共享计数
循环引用发生时具体卡在哪一步
典型场景是两个对象互相用 shared_ptr 持有对方,比如父子节点、观察者与被观察者。它们的控制块彼此强引用,导致共享计数永远不为 0 —— 即使外部所有 shared_ptr 都已离开作用域,对象也无法释放。
现象很直接:程序运行中内存持续上涨,Valgrind 或 ASan 可能报“still reachable”块,但不会崩溃;你查 shared_ptr::use_count() 会发现始终 ≥2。
立即学习“C++免费学习笔记(深入)”;
- 不是内存泄漏检测工具报错,而是对象生命周期被意外延长
- 调试时可在析构函数里打日志,确认它们根本没被调用
- 用
std::cout 快速验证是否存在滞留引用
用 std::weak_ptr 打断循环的具体写法
把循环中「非拥有关系」那一端换成 weak_ptr。比如父节点拥有子节点(shared_ptr),子节点反向持有父节点就该用 weak_ptr;访问前调用 lock() 转成临时 shared_ptr,确保安全。
struct Node {
std::shared_ptr<Node> parent;
std::vector<std::shared_ptr<Node>> children;
};
// ❌ 错误:children 里每个 node 又持有 parent 的 shared_ptr
// ✅ 正确:parent 改成 weak_ptr,访问时 lock()
struct Node {
std::weak_ptr<Node> parent;
std::vector<std::shared_ptr<Node>> children;
};
-
weak_ptr::lock()返回shared_ptr,若原对象已析构则返回空指针,必须判空 - 不要在构造函数里直接调用
lock()并保存结果——可能引发初始化顺序问题 - 避免在 lambda 捕获中隐式提升
weak_ptr为shared_ptr后长期持有
还有哪些容易被忽略的循环引用场景
除了显式的对象互持,闭包、回调、缓存结构也常埋雷。比如用 std::function 存绑定了 this 的 lambda,而 this 又被某个 shared_ptr 管理 —— 这时 lambda 实际持有了 shared_ptr<t></t>,形成闭环。
- 用
[this]捕获时,如果this所在对象由shared_ptr管理,等价于捕获了shared_ptr - 改用
[weak_this = weak_from_this()](需继承std::enable_shared_from_this),并在 lambda 内部auto ptr = weak_this.lock(); if (!ptr) return; - 缓存容器(如
std::map<int std::shared_ptr>></int>)若同时被缓存项反向引用,也会锁死
最麻烦的不是写错,而是循环不发生在同一层代码里 —— 它可能跨多个模块、经过信号槽或线程队列。一旦怀疑,优先检查所有 shared_ptr 成员变量和回调绑定逻辑。










