Item20:当shared_ptr可能悬空时使用weak_ptr
std::weak_ptr 是 std::shared_ptr 的辅助智能指针,不独立存在,特点如下:
- 不参与资源所有权共享,不影响指向对象的引用计数
- 能检测自身是否悬空(过期),即指向的对象是否已被销毁
- 无法直接解引用、无法直接判空,需借助
std::shared_ptr实现安全访问
基础用法
创建
从 std::shared_ptr 直接创建,两者指向同一对象,且不会增加对象的引用计数:
1 | auto spw = std::make_shared<Widget>(); // 对象引用计数(RC)=1 |
安全访问对象(解决竞态条件)
单独调用 expired() 判断是否过期存在竞态风险(检查后到访问前,对象可能被其他线程销毁),需通过原子操作同时完成 “检查 + 访问”,有两种方式:
| 方式 | 操作 | 效果 |
|---|---|---|
lock() 成员函数 |
auto sp = wpw.lock(); |
若 wpw 未过期,返回指向对象的 shared_ptr;若过期,返回空 shared_ptr |
shared_ptr 构造函数 |
std::shared_ptr<Widget> sp(wpw); |
若 wpw 未过期,构造成功;若过期,抛出 std::bad_weak_ptr 异常 |
应用场景
std::weak_ptr 用于处理 shared_ptr 可能悬空的场景,有三类:
1. 可自动清理的缓存(工厂函数优化)
需求:工厂函数加载对象的操作昂贵,需缓存结果;同时要求对象无人使用时,缓存自动销毁,避免占用资源
实现方案:
- 缓存容器存储
std::weak_ptr<const Widget>,不影响对象生命周期 - 工厂函数返回
std::shared_ptr<const Widget>,由调用者管理对象生命周期 - 每次请求时,通过
lock()检查缓存对象是否有效,无效则重新加载并缓存
- 缓存容器存储
1 | std::shared_ptr<const Widget> fastLoadWidget(WidgetID id) |
2. 观察者模式的观察者列表
- 需求:被观察者(subject)需持有观察者(observer)的指针,用于状态变更通知;但被观察者不控制观察者的生命周期,需避免访问已销毁的观察者
- 实现方案:被观察者的观察者列表存储
std::weak_ptr<Observer>,通知前通过lock()检查观察者是否有效,无效则跳过
3. 打破 std::shared_ptr 循环引用
- 问题:若两个对象互相持有对方的
std::shared_ptr,会形成循环引用,导致引用计数无法归零,对象无法销毁,造成内存泄漏 - 解决:将其中一方的
shared_ptr替换为weak_ptr,weak_ptr不影响对方的引用计数,打破循环 - 补充:严格分层结构(如树)中,子节点生命周期由父节点管理,子到父的反向引用可用原始指针,无需
weak_ptr
效率特性
- 大小:与
std::shared_ptr相同 - 底层:与
std::shared_ptr共用控制块 - 性能:构造、析构、赋值操作涉及原子操作,效率与
shared_ptr基本一致 - 引用计数细节:控制块存在两个引用计数,
weak_ptr操作的是第二个计数
总结
- 用
std::weak_ptr替代可能悬空的std::shared_ptr,避免悬空访问的未定义行为 std::weak_ptr的三大典型场景:缓存、观察者列表、打破 shared_ptr 循环引用
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论
