C++bad_weak_ptr弱指针失效异常

2026-04-11 07:10:34 1218阅读 0评论

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_beforelock() 做轻量探测:

    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,它出生时,有没有人给它办过‘落户’?

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,1218人围观)

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

目录[+]