EMCPP条款20:对于类似 std::shared_ptr 但有可能空悬的指针使用std::weak_ptr
std::weak_ptr 并不是一种独立的智能指针,而是 std::shared_ptr 的一种扩充。这种关系在指针生成的时刻就已存在。std::weak_ptr 一般者是通过 std::shared_ptr 来创建的。当使用 std::shared_ptr 完成初始化 std::weak_ptr 的时刻,两者就指涉到了相同位置,但 std::weak_ptr 并不影响所指涉到的对象的引用计数:
1 | // after spw is constructed, the pointed-to Widget's |
std::weak_ptr 的空悬,也被称作失效(expired)。你需要的是一个原子操作来完成 std::weak_ptr 是否失效的校验,以及在未失效的条件下提供对所指涉到的对象的访问。这个操作可以通过由 std::weak_ptr 创建 std::shared_ptr 来实现。该操作有两种形式,选择哪一种取决于由 std::weak_ptr 来创建 std::shared_ptr 时若该 std::weak_ptr 失效,期望得到什么结果。
- 一种形式是
std::weak_ptr::lock, 它返回一个std::shared_ptr。如果std::weak_ptr已经失效,则std::shared_ptr为空:1
2
3
4std::shared_ptr<Widget> spw1 = wpw.lock();
// if wpw's expired, spw1 is null
auto spw2 = wpw.lock();
// same as above, but uses auto - 另一种形式是用
std::weak_ptr作为实参来构造std::shared_ptr。这样,如果std::weak_ptr失效的话,抛出异常:1
2std::shared_ptr<Widget> spw3(wpw);
// if wpw's expired, throw std::bad_weak_ptr
考虑一个工厂函数,该函数基于唯一ID来创建一些指涉到只读对象的智能指针。构造函数成本高昂,需要缓存。缓存管理器的指针需要能够校验它们何时会空悬,因为工厂函数的用户用完由该工厂函数返回的对象后,该对象就将被析构,此时相应的缓存条目将会空悬。这里是带缓存的 loadWidget 的一个快速而粗糙的实现版本:
1 | std::shared_ptr<const Widget> fastLoadWidget(WidgetID id) { |
观察者设计模式 (Observer design pattern) 也会用到
std::weak_ptr,参见原文
要点速记
- 使用
std::weak_ptr来代替可能空悬的std::shared_ptr。 std::weak_ptr可能的用武之地包括缓存,观察者列表,以及避免std::shared_ptr指针环路。