weak_ptr能打破shared_ptr循环引用是因为它不增加引用计数,仅观察对象,不参与所有权管理;当所有shared_ptr销毁后对象立即释放,lock()返回空指针,避免悬挂访问。

weak_ptr 为什么能打破 shared_ptr 循环引用
因为 weak_ptr 不增加引用计数,它只是“观察”一个由 shared_ptr 管理的对象,不参与所有权控制。当所有 shared_ptr 都销毁后,对象立即释放,此时再调用 weak_ptr::lock() 会返回空 shared_ptr,不会导致悬挂或访问已释放内存。
典型循环引用场景及 weak_ptr 修复写法
常见于双向链表节点、父子对象(如树结构)、观察者模式中回调持有宿主指针等。错误写法是双方都用 shared_ptr 互相持有:
struct Parent;
struct Child {
std::shared_ptr<Parent> parent; // ❌ 这里造成循环引用
};
struct Parent {
std::shared_ptr<Child> child;
};
修复方式:把“非拥有关系”的一方改为 weak_ptr:
struct Child {
std::weak_ptr<Parent> parent; // ✅ 不增加引用计数
};
// 使用时需先提升为 shared_ptr
void do_something() {
auto p = parent.lock(); // 返回 shared_ptr,可能为空
if (p) {
// 安全使用 p
}
}
- 只在真正需要访问对象时才调用
lock(),避免频繁提升 - 提升失败(返回空)说明对象已被销毁,必须做空检查
-
weak_ptr本身线程安全(拷贝/赋值/比较无数据竞争),但lock()后的shared_ptr使用仍需业务层同步
weak_ptr::lock() 和 expired() 的选择差异
两者都用于判断所指对象是否还存在,但语义和性能略有不同:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
-
expired()只读状态检查,开销略小;适合“只判断不使用”的场景(如清理缓存) -
lock()是原子性“检查+提升”,返回一个可用的shared_ptr;适合后续要实际访问对象的场景 - 不要写
if (!w.expired()) { auto s = w.lock(); ... }—— 这有竞态风险:两次调用之间对象可能已被销毁 - 正确写法是直接
auto s = w.lock(); if (s) { ... }
容易被忽略的边界问题
很多人以为只要用了 weak_ptr 就万事大吉,但实际还有几个坑:
-
weak_ptr构造只能来自同源shared_ptr(或另一个weak_ptr),不能从裸指针或unique_ptr构造 - 如果对象是通过
make_shared创建的,weak_ptr和shared_ptr共享同一块控制块;但若混用new+shared_ptr构造,控制块分配方式不同,不过不影响weak_ptr行为 - 在析构函数里调用
weak_ptr::lock()是危险的 —— 对象正在销毁,即使lock()成功返回非空shared_ptr,该shared_ptr的析构也会再次触发对象析构(UB)
最稳妥的做法是:确保 weak_ptr 所指向的对象生命周期明确,且“观察方”不参与资源释放决策。

















