expired()仅检查控制块是否存活,返回false不保证对象未析构,安全访问必须依赖lock()成功获取非空shared_ptr。

std::weak_ptr::expired() 是最直接的判断方式
它专为这个目的设计,比 lock() 后判空更轻量——不构造 shared_ptr,只查控制块里的引用计数是否归零。内部通常就是原子读一次弱引用计数,没锁开销。
常见错误是以为 expired() 返回 false 就代表对象“一定还活着且能安全访问”,其实不是:它只说明控制块还在,但对象可能刚被析构、还没清理控制块(比如析构函数里还有异步任务在用 weak_ptr)。所以 expired() == false 只是必要不充分条件。
-
expired()返回true→ 对象肯定已析构,控制块也释放了,绝对不能调用lock() -
expired()返回false→ 控制块尚存,但对象可能正处在析构中途;必须紧接着用lock()获取shared_ptr才能确认能否访问 - 不要用
use_count() == 0替代expired(),因为弱引用存在时use_count()可能为 0 但控制块未销毁
lock() 成功才是安全访问对象的唯一依据
lock() 不仅检查对象状态,还会尝试原子地将弱引用升级为强引用。只有它返回非空 shared_ptr,才真正保证:对象尚未析构 + 当前线程可安全访问。
典型误用是先 if (!wp.expired()) { auto sp = wp.lock(); /* 然后用 sp */ } —— 这中间有竞态窗口:expired() 返回 false 后、lock() 执行前,对象可能已被析构。正确写法是跳过 expired(),直接 auto sp = wp.lock(); if (sp) { /* 用 sp */ }。
立即学习“C++免费学习笔记(深入)”;
- 每次需要访问对象时,都应独立调用
lock(),别缓存结果(除非你明确知道生命周期) -
lock()失败返回空shared_ptr,不是异常,不用 try/catch - 在回调、定时器、线程池等异步场景中,这是唯一靠谱的存活检查手段
为什么不能靠 try-catch 捕获访问崩溃?
C++ 中通过已析构对象的指针访问内存,属于未定义行为(UB),不是 C++ 异常。不会抛出 std::bad_weak_ptr 或其他异常——那玩意儿只在 weak_ptr 本身未初始化或被 move 走后调用 lock() 才抛。对象析构后访问,大概率直接 segfault 或静默错乱,catch 不到。
-
std::bad_weak_ptr的触发条件是:weak_ptr构造自空shared_ptr,或已被 move,或从未赋值 - 对象析构 ≠
weak_ptr变成“坏的”,它依然合法,只是lock()返回空 - 依赖信号处理(如 SIGSEGV)捕获这种错误,在多线程下不可靠且难以调试
一个容易被忽略的细节:自析构对象中的 weak_ptr
如果对象在自己的成员函数里调用 shared_from_this() 得到 shared_ptr,再转成 weak_ptr,然后又在析构函数里去 lock() —— 这时候 lock() 一定失败,但 expired() 可能还返回 false。因为控制块销毁晚于对象析构,而析构函数执行期间控制块仍存在。
这意味着:在对象内部持有自身 weak_ptr 并试图“观察自己是否将死”,逻辑上就不可靠。这种模式本身就有问题,应避免。
- 自管理生命周期时,优先用
shared_from_this()+ 显式reset(),而非依赖weak_ptr回查 - 若必须用
weak_ptr做延迟清理,确保所有lock()调用都在对象外部上下文中 - 控制块销毁时机由最后一个
shared_ptr和最后一个weak_ptr共同决定,不是对象析构那一刻










