【发布时间】:2019-09-26 16:21:00
【问题描述】:
我了解哪些方法可用以及它们是什么。请描述weak_ptr 类的私有部分或给出一些自定义weak_ptr 代码的示例。我无法通过 std::weak_ptr 实现来理解。
【问题讨论】:
我了解哪些方法可用以及它们是什么。请描述weak_ptr 类的私有部分或给出一些自定义weak_ptr 代码的示例。我无法通过 std::weak_ptr 实现来理解。
【问题讨论】:
非侵入式共享指针实现通常包含指向某个动态分配的“状态”的指针,它计算对原始对象的引用数。
当一个共享指针被拷贝时,拷贝得到同一个指向同一个“状态”的指针,“状态”内的计数递增表示现在有两个共享指针共享资源。
当共享指针被销毁时,它递减计数器以指示现在共享资源的指针减少了。如果这导致计数器读数为零,则资源被销毁。
弱指针也有一个指向这个“状态”的指针,但它不递增或递减计数器。当被询问时,它将使用相同的状态构造一个共享指针,但前提是计数不为零。如果计数为零,则最后一个共享指针已经破坏了资源,我们无法再访问它。
有趣的是,您还需要这样的逻辑来控制“状态”对象的生命周期。 :) (我想这是使用第二个计数器实现的,shared_ptr 和 weak_ptr 都会增加,但不要引用我的话。)
(your data) (ref. counters)
║ ║
[resource] [state]
┆ │ │ │ │ │
┆ │ └─[shared_ptr]───┘ │ │
┆ └───[shared_ptr]─────┘ │
└┄┄┄┄┄┄┄[weak_ptr]────────┘
当然,任何特定 std::weak_ptr 实现的私有部分的具体外观取决于编写它的人。
顺便说一句,如果您怀疑它指向的资源可能已经由 shared_ptr( s) 其他地方:你会得到第二个不相关的“状态”对象,你的计数器会出错,你的资源可能会过早地被销毁(如果存在这样的概念,肯定会被销毁两次),造成混乱。
【讨论】:
为了理解链接的共享-弱对,您必须首先想象一个没有任何“弱引用”功能的纯共享“智能指针”。为了提高效率,想象它是一个侵入性的拥有引用计数指针:计数器是托管对象的一部分。
回到“弱引用”特性的实现:我们需要一个能够检测所指对象是否还活着并(原子地)绑定到它的真实(强)引用的对象。 “仍然活着”的测量需要在始终存在的引用计数上完成(直到它不再需要,当有零引用,强或弱时),所以“弱引用”需要是能够保持那个计数器活着。因此,“弱引用”是对侵入式计数数据结构的拥有(强)引用,该结构保持外部计数数据结构的引用计数,T 类型的托管对象weak_ptr<T>。
当没有更多真实(强)引用(shared_ptr)时,实现必须销毁T 类型的托管对象,因此它必须有一个原子计数器用于此目的。请注意,从技术上讲,它不一定是某种原子整数,但 atomic<int> 恰好是获得这种效果的一种简单方法。操作是:
shared_ptr 到 shared_ptr):以原子方式递增计数weak_ptr 到 shared_ptr):原子测试计数是否非零,然后增加并指示成功,否则不修改计数并指示失败所有这些操作都可以在原子变量上进行。它们都涉及原子 RMW(读取修改写入),这通常比简单的读取或写入要昂贵得多。但是,如果该位置在本地 L1d 缓存中可用并且不在 CPU 之间反弹,则成本会更低。
另一个计数是针对侵入式计数的对象:弱引用的计数必须分开,因为即使T 类型的对象已被销毁,它也可以不为零。
对管理数据结构的计数的唯一操作是递增和递减和测试,因为对于稳定的对象,永远无法达到零值:零表示正在销毁的对象。
当然,通常只有一个词可以原子操作,每个计数器通常是一个词,因为在某些情况下半个词可能会溢出,但一个词不能(因为你没有内存来创建 2 ** 31对象,甚至 2 ** 63 对于 64 位机器);所以对计数器的修改有限制。一个简单的解决方案是,侵入式引用计数不跟踪强所有者的更改,这已经很好地保存在一个计数器中,不需要在另一个计数器中复制。侵入式计数器仅用于对T 的最后一个强引用和对T 的弱引用(此处为weak_ptr),它们也是对管理数据结构的强引用。
所以有两个原子计数器:
T 何时被销毁(以及它的内存被释放,除非分配与make_shared 一样与管理数据共享时
其他变体可能是可能的,如果您放弃线程安全,您可以获得更多创意(您可以想象所有者的链接列表),但这些可能不会更有效。
【讨论】: