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