弱_ptr 是如何工作的?

我知道如何使用 weak_ptrshared_ptr。我了解 shared_ptr是如何工作的,通过计算其对象中的引用数量。weak_ptr是如何工作的?我试着通读了一下 Boost 的源代码,但是我对 Boost 还不够熟悉,不能理解它所使用的所有东西。

谢谢。

27263 次浏览

shared_ptr使用一个额外的“计数器”对象(即“共享计数器”或“控制块”)来存储引用计数。 (顺便说一下: 那个“ counter”对象也存储了删除器。)

每个 shared_ptrweak_ptr都包含一个指向实际指针的指针,以及另一个指向“ counter”对象的指针。

为了实现 weak_ptr,“ counter”对象存储两个不同的计数器:

  • “使用计数”是指向对象的 shared_ptr实例的数量。
  • “弱计数”是指向对象的 weak_ptr实例的数量,如果“使用计数”仍然 > 0,则加上一个。

当“使用计数”达到零时,指针被删除。

当“弱计数”达到零时(这意味着“使用计数”也必须为零,见上文) ,“计数器”助手对象将被删除。

当您尝试从 weak_ptr中获取 shared_ptr时,库会自动检查“使用计数”,如果 > 0则增量。如果成功,你得到你的 shared_ptr。如果“ use count”已经为零,则会得到一个空的 shared_ptr实例。


编辑 : 现在,为什么他们在弱计数上加一,而不是在两个计数都为零时释放“计数器”对象?问得好。

另一种方法是在“使用计数”和“弱计数”都降为零时删除“计数器”对象。第一个原因是: 不可能在每个平台上自动检查两个(指针大小)计数器,即使在它所在的位置,也比只检查一个计数器要复杂得多。

另一个原因是,删除器必须保持有效,直到它完成执行。由于删除器存储在“ counter”对象中,这意味着“ counter”对象必须保持有效。考虑一下,如果对某个对象有一个 shared_ptr和一个 weak_ptr,并且它们在并发线程中同时重置,会发生什么情况。假设 shared_ptr是第一位的。它将“使用次数”减少到零,并开始执行删除。现在,weak_ptr将“弱计数”减少到零,并且发现“使用计数”也为零。因此,它删除“ counter”对象,并随之删除。当删除器还在运行的时候。

当然,确保“计数器”对象保持活性的方法有很多种,但我认为将“弱计数”增加一个是一个非常优雅和直观的解决方案。“弱计数”成为“计数器”对象的引用计数。由于 shared_ptr也引用计数器对象,它们也必须增加“弱计数”。

一个可能更直观的解决方案是增加每一个 shared_ptr的“弱计数”,因为每一个 shared_ptr保持对“计数器”对象的引用。

为所有 shared_ptr实例添加一个实例只是一种优化(在复制/分配 shared_ptr实例时节省一个原子增量/减量)。

基本上,“孱弱 _ ptr”是一个普通的“ T *”指针,它允许您在代码的稍后部分恢复一个强引用,即“ share _ ptr”。

就像普通的 T * 一样,弱 _ ptr 不执行任何引用计数。在内部,为了支持对任意类型 T 的引用计数,STL (或实现此类逻辑的任何其他库)创建了一个我们称为“ Anchor”的包装器对象。“ Anchor”的存在仅仅是为了实现引用计数和我们需要的“当计数为零时,调用删除”行为。

在强引用中,share _ ptr 实现它的拷贝、操作符 = 、构造函数、析构函数和其他相关 API,以更新“ Anchor”的引用计数。这就是 share _ ptr 如何确保您的“ T”的存在时间恰好与有人使用它的时间一样长。在弱 _ ptr 中,这些相同的 API 只是复制实际的 Anchor ptr。他们不更新参考计数。

这就是为什么最重要的 API“孱弱 _ ptr”是“过期的”,而“锁”命名不当的原因。“过期”告诉您底层对象是否仍然存在——例如,“它是否已经删除了自己,因为所有强引用都超出了作用域?”.“ Lock”将(如果可以的话)将孱弱 _ ptr 转换为强引用 share _ ptr,恢复引用计数。

顺便说一句,“ lock”对于那个 API 来说是个糟糕的名字。你不(仅仅)调用互斥对象,你从一个弱引用创建一个强引用,“锚”的行为。这两个模板最大的缺陷是它们没有实现 operation-> ,因此要对对象进行任何操作,都必须恢复原始的“ T *”。他们这样做主要是为了支持“ share _ ptr”之类的操作,因为原语类型不支持“->”操作符。