Item 20: Use std::weak_ptr
for std::shared_ptr
-like pointers that can dangle
std::weak_ptr
不是一个独立的智能指针。它是std::shared_ptr
的增强。std::weak_ptr
不能解引用,不会影响所指对象的引用计数。
1 | auto spw = //spw创建之后,指向的Widget的 |
std::weak_ptr
通常从std::shared_ptr
上创建,并使用expired()
检查它是否悬空。
1 | if (wpw.expired()) … //如果wpw没有指向对象… |
std::weak_ptr
如果没有悬空,那我们肯定是想访问它的对象的。考虑到它没有解引用操作,我们可以通过从std::weak_ptr
创建std::shared_ptr
来实现访问所指对象:
- 使用
std::weak_ptr::lock
,它返回一个std::shared_ptr
,如果std::weak_ptr
悬空,std::shared_ptr
为空。 - 以
std::weak_ptr
为实参构造std::shared_ptr
。如果std::weak_ptr
悬空,会抛出一个异常。
1 | std::shared_ptr<Widget> spw1 = wpw.lock(); //如果wpw过期,spw1就为空 |
std::weak_ptr 有三种主要用法:
缓存
我们希望将一些计算结果存储在缓存中, 缓存对象的指针需要知道它是否已经悬空。所以缓存应该使用
std::weak_ptr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
static std::unordered_map<WidgetID,
std::weak_ptr<const Widget>> cache;
//译者注:这里std::weak_ptr<const Widget>是高亮
auto objPtr = cache[id].lock(); //objPtr是去缓存对象的
//std::shared_ptr(或
//当对象不在缓存中时为null)
if (!objPtr) { //如果不在缓存中
objPtr = loadWidget(id); //加载它
cache[id] = objPtr; //缓存它
}
return objPtr;
}观察者设计模式
观察者设计模式有observers观察的subjects。每个subjects都持有指向其observers的指针。因此如果subjects状态发生变化, 它可以向observers发送更新。但它必须确保如果observers被销毁后不再访问它。std::weak_ptr 是此处使用的正确指针类型。
循环引用
有一个用例, A 持有 B 的一个 std::shared_ptr,但 B 也想指向回 A.,如下图所示:
- 使用原始指针:如果 A 被销毁, B 可能会无意中引用到悬挂指针,不好。
std::shared_ptr
:A和B都互相持有对方的std::shared_ptr
,导致了循环引用,错误。std::weak_ptr
:尽管A和B互相指向对方,B的指针不会影响A的引用计数,正确。
从效率角度来看,std::weak_ptr
与std::shared_ptr
基本相同。
重点
- 将 std::weak_ptr 替代可能会悬空的 std::shared_ptr 。
- std::weak_ptr 的潜在使用场景包括缓存、观察者列表和打破 std::shared_ptr 循环结构。