std::shared_ptr循环引用会导致内存泄漏,因引用计数无法归零;需用std::weak_ptr打破强引用链,其不增引用计数,须通过lock()获取临时shared_ptr访问对象。

std::shared_ptr 循环引用会导致内存泄漏
当两个 std::shared_ptr 相互持有对方所管理的对象时,引用计数永远无法归零,对象不会被析构 —— 这就是循环引用。它不报错、不崩溃,但内存持续增长,是典型的“静默泄漏”。
用 std::weak_ptr 打断强引用链
std::weak_ptr 不增加引用计数,只“观察”对象是否还活着。它不能直接访问对象,必须通过 lock() 转成 std::shared_ptr 才能使用;若原对象已销毁,lock() 返回空的 std::shared_ptr。
常见做法是:一方用 std::shared_ptr 拥有另一方,另一方用 std::weak_ptr 回指——比如父类持子类的 std::shared_ptr,子类持父类的 std::weak_ptr。
struct Parent;
struct Child {
std::weak_ptr parent; // 不参与所有权,不增加引用计数
};
struct Parent {
std::shared_ptr child;
};
容易踩的坑:误用 weak_ptr::lock() 或直接构造
- 直接用
std::shared_ptr构造(即拷贝构造)会触发未定义行为 ——(parent) std::weak_ptr不能直接转std::shared_ptr,必须调用lock() -
lock()返回的是临时std::shared_ptr,如果只用于条件判断但没保存,后续再访问可能已失效 - 在析构函数里调用
lock()是安全的,但若此时对象正被销毁(比如Parent析构中访问child->parent.lock()),lock()会返回空,这是预期行为
调试循环引用的实用技巧
没有编译期检查,只能靠设计约束和运行时辅助:
立即学习“C++免费学习笔记(深入)”;
- 用
use_count()打印关键节点的引用数(仅 debug 版),比如ptr.use_count()看是否异常偏高 - 避免在类内部用
shared_from_this()向自己创建循环,尤其在回调注册场景下 - 考虑用 RAII 容器(如
std::vector<:shared_ptr>>)替代双向链表式结构,减少手动管理指针的必要
最麻烦的地方不是写 weak_ptr,而是判断哪边该强、哪边该弱——这取决于对象生命周期的自然归属关系,一旦定错,问题会藏得很深。










