weak_ptr 通过不增加引用计数来破循环引用,仅以lock()安全获取临时shared_ptr,必须判空使用,不可绕过lock()直接解引用,且需定期清理容器中失效的weak_ptr。

weak_ptr 怎么破循环引用
循环引用不是 weak_ptr 的“功能”,而是它存在的唯一理由。只要你用 shared_ptr 把两个对象互相持有,又没在某处主动断开,引用计数就永远不归零——对象既不会析构,内存也不会释放。
weak_ptr 不增加引用计数,只“观察” shared_ptr 管理的对象是否还活着。它本身不参与所有权,所以不会把引用计数拉高,也就不会拖住对方的生命周期。
- 典型场景:父类持有一个
shared_ptr指向子类,子类又通过回调或成员变量反向持有父类的shared_ptr - 正确做法:子类改用
weak_ptr<parent></parent>存父类指针,用时调lock()拿临时shared_ptr,拿不到说明父类已析构 - 错误写法:
std::weak_ptr<t> wp = sp; wp.lock()->do_something();</t>—— 没判空直接调用,lock()返回空shared_ptr时解引用会崩
lock() 返回空 shared_ptr 怎么安全用
weak_ptr::lock() 是唯一能从 weak_ptr 拿到有效 shared_ptr 的方式,但它可能返回空。这不是异常,是常态——对象可能刚被析构,而你还没来得及感知。
别把它当“获取指针”的快捷方式,要当成一次“尝试租借”:租到了才能用,租不到就得跳过或 fallback。
立即学习“C++免费学习笔记(深入)”;
- 必须检查:
auto sp = wp.lock(); if (sp) { sp->do_work(); } - 不能写成:
wp.lock()->do_work();(未判空)或if (wp.expired()) return;(多一次查表,且仍需 lock() 才能用) - 性能上,
lock()是原子读,比expired()多一次引用计数递增,但这是必要代价——你要的是“此时此刻能安全用”,不是“它曾经存在过”
weak_ptr 不能直接构造 shared_ptr 的坑
weak_ptr 没有隐式转 shared_ptr 的构造函数,也不支持 *wp 或 wp.get()。所有试图绕过 lock() 直接取用的写法,编译不过或行为未定义。
常见误操作是想“先确认再用”,结果写出竞态:
- 错:
if (!wp.expired()) { wp.lock()->do_work(); }——expired()和lock()之间对象可能已被析构 - 对:
if (auto sp = wp.lock()) { sp->do_work(); }—— 原子性地“检查+租借” - 注意:
weak_ptr构造只能来自shared_ptr或另一个weak_ptr,不能从裸指针、unique_ptr或auto_ptr(已弃用)构造
weak_ptr 在容器里怎么避免悬空
把 weak_ptr 存进 std::vector、std::map 等容器很常见,比如缓存、监听器列表。但容器本身不管理生命周期,容易存了一堆已失效的 weak_ptr。
不清理不是 bug,但长期积累会拖慢 lock()(内部要查控制块),也增加误判风险。
- 建议定期清理:遍历容器,用
lock()尝试升级,失败就erase - 不要依赖析构自动清理——容器析构时
weak_ptr自动销毁,但之前指向的对象早没了 - 如果容器元素极少且生命周期明确(比如仅限当前函数作用域),可不清理;高频增删+长期存活的容器,必须加清理逻辑
weak_ptr 的本质是“延迟判断所有权”,它不解决设计问题,只帮你更安全地应对所有权边界模糊的场景。真正难的从来不是怎么写 lock(),而是想清楚谁该拥有谁、谁该观察谁——这点没理清,再多 weak_ptr 也救不了循环引用。










