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
指针环路。