weak_ptr 包含哪些变量?

What variables does weak_ptr hold?

我了解可用的方法以及它们是什么。请描述 weak_ptr class 的私人部分或给出一些自定义 weak_ptr 代码的示例。我无法通过 std::weak_ptr 实现来理解。

非侵入式共享指针实现通常包含指向某些动态分配的指针 "state",它计算对原始对象的引用数量。

复制共享指针时,副本得到相同的指针指向相同的"state","state"内的计数增加到表明现在有两个共享指针共享资源。

当共享指针被销毁时,它会递减 计数器以指示现在共享资源的指针减少了一个。如果这导致计数器读数为零,则资源将被销毁。

弱指针也有指向此 "state" 的指针,但它不会 增加或减少计数器。当被询问时,它将使用相同的状态构造一个共享指针,但前提是计数不为零。如果计数为零,则最后一个共享指针已经破坏了资源,我们无法再访问它。

有趣的是,您还需要这样的逻辑来控制"state" 对象的生命周期。 :) (我想这是使用第二个计数器实现的,shared_ptrweak_ptr 都会递增,但不要引用我的话。)

(your data)         (ref. counters)
     ║                    ║
[resource]             [state]
  ┆  │ │                │ │ │
  ┆  │ └─[shared_ptr]───┘ │ │
  ┆  └───[shared_ptr]─────┘ │
  └┄┄┄┄┄┄┄[weak_ptr]────────┘

当然,任何特定 std::weak_ptr 实现的私有部分到底是什么样子取决于编写它的人。

顺便说一下,如果您怀疑它指向的资源可能 已经 ,那么该图显示了为什么您不应该从原始指针构造 shared_ptr由其他地方的 shared_ptr(s) 管理:你会得到第二个不相关的 "state" 对象,你的计数器将是错误的,你的资源可能会过早销毁(并且肯定会被销毁两次,如果存在这样的概念),造成混乱。

为了理解链接的共享弱对,您必须首先想象一个没有任何 "weak reference" 特征的纯共享 "smart pointer"。为了提高效率,想象它是一个侵入性的拥有引用计数的指针:计数器成为托管对象的一部分。

回到 "weak reference" 特性的实现:我们需要一个对象,它可以检测一个引用对象是否还活着,并绑定一个真实的(强)引用到它(原子地)。 "is still alive" 的测量需要在一直存在的引用计数上进行(直到不再需要它,当引用为零时,强或弱),因此 "weak reference" 需要能够使该计数器保持活动状态。因此 "weak reference" 是对侵入式计数数据结构的拥有(强)引用,该数据结构保持外部计数数据结构的引用计数,weak_ptr<T> 类型 T 的托管对象。

实现必须恰好在没有更多真实(强)引用(shared_ptr)时销毁类型 T 的托管对象,因此它必须具有用于该目的的原子计数器。请注意,从技术上讲,它不一定是某种原子整数,但 atomic<int> 恰好是获得该效果的简单方法。操作是:

  • 通过其他所有者的副本创建所有者(shared_ptrshared_ptr):自动递增计数
  • 所有者销毁和测试:自动递减并测试最后一个所有者(即计数为零)
  • owner creation from weak reference (weak_ptr to shared_ptr): 原子测试count是否非零,然后增加并指示成功,否则不修改计数并指示失败

所有这些操作都可以在原子变量上进行。它们都涉及原子 RMW(读取修改写入),这通常比简单的读取或写入要昂贵得多。但是,如果该位置在本地 L1d 缓存中可用并且不会在 CPU 之间跳动,则成本会更低。

另一个计数是针对侵入式计数的对象:弱引用的计数必须是独立的,因为即使 T 类型的对象已被销毁,它也可能不为零。

对管理数据结构的计数进行的唯一操作是递增和递减并测试,因为对于稳定对象永远无法达到零值:零表示正在被销毁的对象。

当然,通常只有一个单词可以被原子操作,每个计数器通常是一个单词,因为在某些情况下半个单词可能会溢出,但一个单词不会(因为你没有内存来创建 2 * * 31 个对象,对于 64 位机器甚至是 2 ** 63 个);所以对计数器的修改有限制。一个简单的解决方案是侵入式引用计数不会跟踪强所有者的更改,这些更改已经很好地保存在一个计数器中,不需要在另一个计数器中复制。侵入式计数器只服务于对T的最后一个强引用和对T(这里的weak_ptr)的弱引用,它们也是对管理数据结构的强引用。

所以有两个原子计数器:

  • 强引用计数:确定 T 何时被销毁(及其内存释放,除非分配与管理数据共享,如 make_shared
  • the (weak reference plus last strong reference) count: 表示存在多少个引用,所有强引用算一个

其他变体可能是可能的,如果您放弃线程安全,您可以获得更多创意(您可以想象所有者的链接列表),但这些可能不会更有效率。