weak_ptr必须用于打破shared_ptr循环引用,防止内存泄漏;它作为非拥有式观察者,需通过lock()安全升级为shared_ptr,且不可在析构函数中调用。

weak_ptr 什么时候必须用,而不是用 shared_ptr
当两个对象互相持有 shared_ptr,导致引用计数永远不为 0、析构不触发时,就必须用 weak_ptr 打断循环。这不是“推荐做法”,是内存泄漏的硬性修复手段。
典型场景:父类存子对象的 shared_ptr,子对象又通过 shared_ptr 回指父类——只要没手动拆解,对象就永远驻留内存。
- 只读访问父对象?用
weak_ptr+lock()检查是否还活着 - 需要长期持有且能确保生命周期?改用原始指针或引用(但要自己管好生命周期)
- 把
weak_ptr存进容器再反复lock()?小心性能——每次调用都涉及原子操作
lock() 返回空 shared_ptr 就代表对象已销毁
weak_ptr::lock() 不是“尝试获取锁”,而是“尝试升级为有效共享所有权”。它返回 shared_ptr,如果原对象已被析构,就返回空(nullptr)。
常见错误:直接解引用 weak_ptr(C++ 不允许),或忽略 lock() 返回值直接用:
立即学习“C++免费学习笔记(深入)”;
auto ptr = wp.lock();
if (ptr) {
ptr->do_something(); // 安全
} else {
// 对象没了,别碰
}
- 不能写
wp->do_something()—— 编译不过 - 不能写
if (wp.expired()) { ... } else { wp.lock()->do_something(); }—— 竞态:两次调用间对象可能已被销毁 -
expired()只适合做快速预检,真正要用还得靠lock()
make_shared 创建的对象,weak_ptr 也能正常 lock
有人误以为 make_shared 会“优化掉 weak_ptr 支持”,其实不会。make_shared 只是把控制块和对象内存合并分配,控制块里依然维护弱引用计数。
所以:weak_ptr 和 shared_ptr 是否来自 make_shared 无关紧要,只要它们指向同一对象,lock() 就有效。
- 用
make_shared<t>()</t>构造后,再用weak_ptr接收没问题 - 混用
shared_ptr(new T)和make_shared同一类型?可以,但控制块分离,弱引用计数不共享——慎用 - 控制块内存释放时机:强引用和弱引用都归零时才释放
循环引用不是 weak_ptr 的唯一用途
weak_ptr 最常被当成“破循环”工具,但它本质是“非拥有式观察者”。比如缓存、监听器列表、回调注册表——这些地方你不想因为观察者存在而阻止目标对象销毁。
例如事件系统中,监听器持有被监听对象的 weak_ptr,每次触发前 lock() 判断是否还有效:
void on_event() {
auto obj = target.lock();
if (obj) obj->handle_event();
else remove_self_from_list(); // 主动清理
}
- 不要在析构函数里调用
lock()—— 控制块可能正在销毁,行为未定义 -
weak_ptr自身不引发线程安全问题,但lock()+ 使用是两步操作,中间可能被其他线程析构 - 如果只是临时检查是否存在,
expired()比lock()轻量;如果紧接着要调用方法,一步lock()更可靠









