weak_ptr 本身不破循环引用,真正破解需将双向 shared_ptr 中的一方改为 weak_ptr 以消除强引用链;它仅用于安全观测,访问前必须 lock() 获取临时 shared_ptr。

weak_ptr 怎么破 C++ 循环引用
直接说结论:weak_ptr 本身不破循环引用,它只是让你「安全地观察」一个可能已销毁的对象;真正破循环引用,靠的是把其中一个 shared_ptr 换成 weak_ptr,让引用计数不再被它参与计算。
常见错误是:两个类互相用 shared_ptr 持有对方,析构时谁都不放手——weak_ptr 插不进去,根本没机会生效。必须从设计上砍掉一条强引用链。
- 只在「观测者」或「非拥有关系」场景用
weak_ptr(比如缓存、回调、父子关系中的子→父) -
weak_ptr不能直接访问对象,必须先调用lock()获得临时shared_ptr,失败说明对象已销毁 - 别在构造函数里直接用
weak_ptr.lock()初始化成员,容易因初始化顺序导致空指针
为什么 lock() 返回空 shared_ptr
不是 weak_ptr 坏了,是它指向的资源已经被最后一个 shared_ptr 释放了。这是正常行为,不是 bug。
典型场景:A 持有 shared_ptr<b></b>,B 持有 weak_ptr<a></a>;当 A 的所有强引用都离开作用域(包括 A 自己的 this),B 再调 weak_ptr.lock() 就返回空。
立即学习“C++免费学习笔记(深入)”;
- 检查是否过早释放了源头的
shared_ptr(比如局部变量提前结束生命周期) - 避免在 lambda 捕获中隐式延长
shared_ptr生命周期,却忘了weak_ptr仍指向原对象 - 调试时可加日志:在目标对象的析构函数里打点,确认销毁时机是否符合预期
shared_ptr 和 weak_ptr 配合的写法要点
关键不是“怎么配”,而是“哪边该用 weak”。多数循环引用发生在双向关联中,比如 Observer/Observable、Parent/Child、Node/Graph。
以 Parent/Child 为例:Parent 拥有 Child,Child 必须知道 Parent —— 这个「知道」不该是拥有关系。
- Parent 用
shared_ptr<child></child>管理生命周期 - Child 用
weak_ptr<parent></parent>存 Parent,访问前if (auto p = parent_.lock()) { ... } - 不要在 Child 构造时就调
parent_.lock()赋值成员,应延迟到实际使用处 - 如果 Child 需要频繁访问 Parent,且确定 Parent 生命周期一定长于自身,可考虑传入 raw pointer(但失去安全性保障)
容易被忽略的线程安全细节
weak_ptr::lock() 是线程安全的,但返回的 shared_ptr 只保你那一刻的“活着”,之后对象仍可能被其他线程销毁。
尤其在多线程回调中,lock() 成功 ≠ 对象全程可用。常见坑是:锁住后做耗时操作,期间对象被析构,后续访问未检查。
- 把
lock()结果存在局部shared_ptr变量里,后续所有访问都用它,避免重复 lock - 若操作分多步且中间有等待(如 sleep、wait、I/O),每一步前都重新
lock()或确保整个流程原子 -
expired()和lock()不是完全等价的:前者只查状态,后者尝试升级并增加引用计数,有轻微开销
最麻烦的从来不是语法怎么写,而是判断「谁才是真正拥有者」——这个决定错了,weak_ptr 再怎么用也只是补丁,不是解药。










