最典型的“过期对象引用”误用是weak_ptr::lock()返回空时直接解引用,导致未定义行为;string_view绑定局部string后跨函数使用、多线程裸指针生存期竞态、lambda捕获this后对象析构,均属同类悬垂访问问题。

std::weak_ptr::lock() 返回空时直接解引用
这是最典型的“过期对象引用”误用:调用 weak_ptr::lock() 后没检查返回的 shared_ptr 是否有效,就直接访问 *ptr 或 ptr->member。一旦原对象已被销毁,lock() 返回空 shared_ptr,解引用会触发未定义行为——可能崩溃、读到垃圾值,或在优化后被编译器静默移除(UB 的典型表现)。
常见于回调、异步任务、观察者模式中:线程A注册了一个带 weak_ptr 的 lambda,线程B稍后执行它,但此时被观察对象早已析构。
- 必须把
lock()结果赋给局部shared_ptr变量,再判断是否非空 - 不要写成
if (auto p = wp.lock()) { use(*p); }这种单行写法——看似安全,但若use()是宏、内联函数或含异常路径,仍可能因生命周期延长出问题 - 避免在锁保护外反复调用
lock():多次调用不保证结果一致,对象可能在两次调用之间销毁
std::string_view 绑定到局部 std::string 后跨函数使用
std::string_view 不拥有数据,只存指针+长度。若它构造时绑定的是栈上 std::string(如函数内临时变量),函数一返回,底层内存立刻失效,后续任何 sv.data()、sv[0]、sv.to_string() 都是未定义行为。编译器通常不会报错,运行时表现高度依赖优化等级和内存布局。
典型错误场景:网络请求解析函数返回 string_view 指向局部 std::string 缓冲区;日志模块接收 string_view 后延迟格式化,但原始字符串早已析构。
- 禁止从局部
std::string构造长期存活的string_view - 若需跨作用域传递字符串视图,优先传
const std::string&或std::string值;仅当明确控制生命周期且性能敏感时才用string_view - 启用
-Wdangling-gsl(Clang)或/wd5038(MSVC)可捕获部分此类问题
多线程中裸指针/引用指向的对象被另一线程提前释放
比智能指针更隐蔽:一个线程用 new 分配对象并传裸指针给其他线程,但释放逻辑(delete)只在创建线程中执行,且无同步。接收线程可能还在用该指针时,对象已被销毁。这不是数据竞争(没共享写),而是“生存期竞态”——对象销毁时机与使用者访问时机无约束。
常见于老代码迁移、第三方库回调、线程池任务参数传递等场景。ASan 能检测到使用已释放内存,但无法指出谁释放的、何时释放的。
- 彻底避免跨线程传递裸指针或引用;必须传递时,改用
shared_ptr确保对象至少活到所有使用者完成访问 - 若必须用裸指针(如对接 C 接口),需设计显式生命周期协议:例如接收方调用
acquire()/release(),由创建方等待所有release()完成后再delete - 不要依赖“对方还没开始用”的主观判断;线程调度不可预测,哪怕加了
sleep也不解决根本问题
lambda 捕获 this 后,对象在异步执行前已被析构
成员函数中启动异步操作(如 std::async、线程池提交、定时器回调),lambda 捕获 this,但外部对象生命周期短于异步任务执行时间。任务真正运行时,this 已指向释放后的内存,访问成员变量或调用成员函数即 UB。
比普通悬空引用更难发现,因为语法合法、编译通过、甚至短期测试都正常——只在高负载、调度延迟或对象快速销毁时暴露。
- 捕获
this前,先用shared_from_this()获取shared_ptr<t></t>,并在 lambda 中捕获该智能指针 - 确保类继承自
std::enable_shared_from_this<t></t>,且对象本身由shared_ptr管理(否则shared_from_this()抛异常或 UB) - 避免在析构函数中启动新异步任务;析构已开始,对象语义上不再有效
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











