发布于2026-07-19 阅读(0)
扫一扫,手机访问
C++ 的 weak_ptr 是个好东西,但也是个容易让人在细节上栽跟头的工具。它不持有引用计数,所以不能像普通指针那样直接解引用,必须通过 lock() 先拿到 shared_ptr 才能安全操作。这个机制背后的逻辑是什么,又该怎么用好它,咱们来拆开聊聊。
原因其实很简单:weak_ptr 本身不持有引用计数,它只是对 shared_ptr 所管理对象的一个“弱观察”。它没有重载 * 和 -> 运算符,所以直接调用 get() 或者解引用,编译阶段就会报错。
正确的做法是调用 lock() 方法,它会返回一个 shared_ptr。如果原来的对象还在,这个 shared_ptr 就是有效的;如果对象已经被释放,返回的就是空 shared_ptr,判断一下 !ptr 就能知道。
wp.get()->do_something() —— 编译直接报错*wp —— 同样编译不过if (auto sp = wp.lock()) { sp->do_something(); }lock() 本身是线程安全的,但检查和实际使用之间仍然存在一个竞态窗口,所以“检查后立刻用”是个关键习惯观察者模式是循环引用的经典温床。典型场景是 Subject 持有一堆 shared_ptr,而 Observer 又持有一个指向 Subject 的 shared_ptr。这样一来,两者互相强引用,析构函数永远别想被调用。
解决办法很简单:Observer 改用 weak_ptr 来存储对 Subject 的引用。这样 Observer 不会增加 Subject 的引用计数,Subject 销毁后 Observer 仍然可以安全存在,只是下次调用 lock() 时会失败。
shared_ptr 给外部使用,内部通知逻辑不变shared_ptr,但内部存成 weak_ptrif (auto subj = subject_wp.lock()) { subj->notify(...); }this 注册进 Subject 后,又在析构函数里反注册。此时 Subject 可能已经销毁,lock() 返回空,反注册逻辑应该静默跳过expired() 是一个轻量级的检查,只读取控制块中的引用计数,不加锁也不增加计数,所以速度很快。lock() 则要原子地尝试将弱引用提升为强引用,开销略高,而且有可能失败。
wp.expired() —— 快,适合日志、断言或快速跳过wp.lock() —— 它同时完成检查和“保活”,避免检查后对象被销毁if (!wp.expired()) { wp.lock()->do_something(); } 这种代码,两次访问控制块之间,对象可能已经被其他线程销毁了,结果就是未定义行为wp.lock().get()->do_something(),万一 lock() 返回空 shared_ptr,get() 返回空指针,解引用直接崩溃有人会试图用裸指针 Subject* 来替代 weak_ptr,觉得“我手动保证不 dangling 就行”。但在多线程、异步回调、延迟执行这些场景下,你根本没法确定 Subject 的生命周期边界,这种做法极其危险。
weak_ptr 的价值不在于“省内存”,而在于提供一种可验证、线程安全的对象存活检查机制std::function、lambda 捕获、消息队列延迟调用的场景,裸指针基本就等于悬垂指针weak_ptr 本身不防逻辑错误。比如误把不同 shared_ptr 创建的 weak_ptr 混用,控制块不一致会导致 lock() 永远失败真正容易被忽略的点是:weak_ptr 必须由同一个 shared_ptr 初始化而来,跨实例赋值或从不同源头构造,会让观察彻底失效。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8