shared_ptr循环引用会导致内存泄漏,因引用计数无法归零;weak_ptr通过不增加引用计数并配合lock()安全访问来打破循环,是标准且常用方案。

shared_ptr 循环引用会导致内存泄漏
当两个 shared_ptr 相互持有对方管理的对象时,引用计数永远无法降到 0,对象不会被析构——这不是 bug,是设计使然。典型场景是双向链表节点、父子对象(如树节点中 parent 和 children)、观察者模式中 observer 和 subject 互相持有时。
weak_ptr 是专为打破循环引用而生的
weak_ptr 不增加引用计数,只“观察”目标对象是否还活着。它不能直接访问对象,必须先调用 lock() 转成 shared_ptr 才能安全使用——如果原对象已销毁,lock() 返回空 shared_ptr。
- 父类中对子节点用
shared_ptr(生命周期由父决定) - 子节点中对父节点用
weak_ptr(避免反向延长父生命周期) - 访问父节点前必须检查:
if (auto p = parent.lock()) { /* 安全使用 p */ } - 不要用
weak_ptr::operator->或解引用,它不存在;必须通过lock()
别在构造函数里直接用 weak_ptr.lock() 初始化成员
常见错误:在子节点构造函数中,把传入的 shared_ptr 立即转成 weak_ptr,再立刻 lock() 赋值给另一个成员——这看似“保险”,实则多余且易误导。此时对象刚构造,parent 肯定还活着,但后续逻辑可能误以为该指针可长期有效。
- 正确做法:只存
weak_ptr,所有访问都走lock()+ 判空 - 错误写法:
parent_ref(parent_ptr), cached_parent(parent_ptr) // cached_parent 是 shared_ptr,又引入循环 -
weak_ptr本身不参与资源管理,拷贝开销极小;频繁lock()的性能影响远小于内存泄漏
还有其他方案,但 weak_ptr 是最常用且标准的
手动管理(比如子节点用裸指针或 raw pointer)可行,但失去自动生命周期保障,容易悬垂;用 shared_ptr 配合自定义删除器也能绕过,但复杂且难维护;enable_shared_from_this 解决的是“从 this 创建 shared_ptr”的问题,和循环引用无直接关系。
立即学习“C++免费学习笔记(深入)”;
- 裸指针仅适用于“子绝不会比父活得久”的确定场景,且需确保父析构前清空所有子的指针
-
enable_shared_from_this的shared_from_this()返回的是当前对象的shared_ptr,若被父节点保存,仍会构成循环 - 真正需要警惕的,是那些看起来“只是读一下 parent”的地方——只要用了
shared_ptr,就可能悄悄锁住整个生命周期
实际中最容易漏掉的,是在 lambda 捕获或异步回调里隐式延长了 shared_ptr 生命周期,导致本该释放的对象一直卡着。这种循环不是结构上的,而是时间维度上的——weak_ptr 同样适用,但得记得在捕获时用 [self = weak_from_this()] 这类写法。










