Effective Modern C++ Item 20

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
2
3
4
5
6
7
8
auto spw =                      //spw创建之后,指向的Widget的
std::make_shared<Widget>(); //引用计数(ref count,RC)为1。
//std::make_shared的信息参见条款21

std::weak_ptr<Widget> wpw(spw); //wpw指向与spw所指相同的Widget。RC仍为1

spw = nullptr; //RC变为0,Widget被销毁。
//wpw现在悬空

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
2
3
std::shared_ptr<Widget> spw1 = wpw.lock();  //如果wpw过期,spw1就为空

std::shared_ptr<Widget> spw3(wpw); //如果wpw过期,抛出std::bad_weak_ptr异常

std::weak_ptr 有三种主要用法:

  1. 缓存

    我们希望将一些计算结果存储在缓存中, 缓存对象的指针需要知道它是否已经悬空。所以缓存应该使用std::weak_ptr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    std::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;
    }
  2. 观察者设计模式

    观察者设计模式有observers观察的subjects。每个subjects都持有指向其observers的指针。因此如果subjects状态发生变化, 它可以向observers发送更新。但它必须确保如果observers被销毁后不再访问它。std::weak_ptr 是此处使用的正确指针类型。

  3. 循环引用

    有一个用例, A 持有 B 的一个 std::shared_ptr,但 B 也想指向回 A.,如下图所示:

    item20_fig2

    • 使用原始指针:如果 A 被销毁, B 可能会无意中引用到悬挂指针,不好。
    • std::shared_ptr:A和B都互相持有对方的std::shared_ptr,导致了循环引用,错误。
    • std::weak_ptr:尽管A和B互相指向对方,B的指针不会影响A的引用计数,正确。

从效率角度来看,std::weak_ptrstd::shared_ptr基本相同。

重点

  • std::weak_ptr 替代可能会悬空的 std::shared_ptr
  • std::weak_ptr 的潜在使用场景包括缓存、观察者列表和打破 std::shared_ptr 循环结构。