C++bad_weak_ptr弱指针失效异常
bad_weak_ptr:那个“还没出生就夭折”的弱指针异常
你有没有试过这样写代码:
std::weak_ptr<int> wp;
auto sp = wp.lock(); // 没问题,sp 是空的
// ……但下一秒:
std::shared_ptr<int> sp2(wp); // boom!
程序直接崩在构造 shared_ptr 时,抛出 std::bad_weak_ptr。不是段错误,不是空指针解引用,而是一个明确、安静、却让人愣住三秒的异常——它不怪你没检查,只怪你“用错了时机”。
这不是弱指针坏了,是它根本没被允许活下来。
弱指针不是“弱”,是“寄生”
std::weak_ptr 本身不管理资源,它只是 shared_ptr 的观察者。它的存在依赖于至少一个“合法主人”——也就是一个还活着的 shared_ptr 持有同一块内存。
关键点来了:weak_ptr 的“合法性”不是靠自己维持的,而是靠构造它的那个 shared_ptr 是否真实存在过、且未被销毁。
常见误用场景有两类:
-
空
weak_ptr直接参与构造std::weak_ptr<int> wp; // 默认构造 → 内部控制块为空 std::shared_ptr<int> sp(wp); // ❌ 抛出 bad_weak_ptr这就像拿一张作废的借条去银行兑现金——纸还在,但背后没账可查。
-
weak_ptr来自已销毁的shared_ptr,且从未 lock 过std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; sp.reset(); // 资源释放,控制块析构 // 此时 wp.lock() 返回空 shared_ptr,但 wp 本身仍“有效” std::shared_ptr<int> sp2(wp); // ✅ 合法!即使 wp.expired() 为 true,也能构造(结果为空)等等?这不矛盾吗?
不矛盾。这里藏着一个容易被忽略的细节:bad_weak_ptr只在weak_ptr根本没有绑定到任何控制块时触发。一旦它曾通过shared_ptr构造成功,哪怕目标早已释放,它仍是“有出身”的——只是“孤儿”而已。
所以真正踩坑的,往往是这种写法:
std::weak_ptr<int> get_weak() {
auto sp = std::make_shared<int>(100);
return sp; // ❌ 错!sp 离开作用域即销毁,返回的 weak_ptr 指向已销毁的控制块
}
// 调用方:
auto wp = get_weak();
std::shared_ptr<int> sp(wp); // 💥 bad_weak_ptr
编译器不会拦你,运行时才翻脸。因为 wp 的内部指针没被清零(标准未强制要求),它指向一块已归还给堆的内存——此时调用构造函数,底层会尝试访问已失效的控制块,从而抛出异常。
怎么一眼识别风险?三个信号灯
-
🔴 默认构造后直接用于
shared_ptr构造
std::weak_ptr<T> wp; auto sp = std::shared_ptr<T>(wp);→ 必崩。 -
🟡 从临时
shared_ptr隐式转换而来,且该shared_ptr生命周期极短
尤其出现在函数返回值、lambda 捕获、或容器emplace场景中。例如:std::vector<std::weak_ptr<int>> vec; vec.emplace_back(std::make_shared<int>(1)); // ❌ make_shared 临时对象构造 weak_ptr 后立即销毁 -
🟢 安全用法:所有
weak_ptr必须由“存活中”的shared_ptr显式赋值或拷贝构造auto sp = std::make_shared<int>(1); std::weak_ptr<int> wp = sp; // ✅ 有主可依 // 或 std::weak_ptr<int> wp(sp); // ✅ 同上
实战建议:别等崩溃,主动设防
与其靠 try-catch 捕获 bad_weak_ptr(它不该是流程一部分),不如把防御做在前面:
-
初始化即校验:若
weak_ptr来源不可控(比如跨模块传参),先用owner_before或lock()做轻量探测:bool is_valid(const std::weak_ptr<int>& wp) { // 注意:不能只靠 expired()!空 weak_ptr 也 expired,但更早就会炸 try { auto _ = wp.lock(); // 不取值,只试探 return true; } catch (const std::bad_weak_ptr&) { return false; } }虽然略重,但比让异常穿透到业务层强。
-
用 RAII 封装“带主弱指针”
如果项目高频使用weak_ptr,可封装一个safe_weak_ptr<T>,在构造时强制要求非空shared_ptr:template<typename T> class safe_weak_ptr { std::weak_ptr<T> wp_; public: explicit safe_weak_ptr(const std::shared_ptr<T>& sp) : wp_(sp) { if (!sp) throw std::invalid_argument("safe_weak_ptr requires non-null shared_ptr"); } std::shared_ptr<T> lock() const { return wp_.lock(); } };把错误拦截在构造期,而不是在下游
shared_ptr构造时。 -
日志加一句“谁造的这个 weak_ptr?”
在调试阶段,对可疑weak_ptr打印其地址和use_count()(需配合自定义删除器或调试构建)。很多时候,bad_weak_ptr的根源不在使用处,而在上游某次“想当然”的赋值。
最后一句实在话
bad_weak_ptr 不是 C++ 的缺陷,它是类型系统在说:“你正在试图从虚无里拉出一个实体。”
它提醒我们:弱指针不是备胎,而是契约——契约成立的前提,是曾经有一个共享指针,郑重地把它介绍给世界。
下次看到这个异常,别急着查文档,先回溯:这个 weak_ptr,它出生时,有没有人给它办过‘落户’?


还没有评论,来说两句吧...