为什么 shared_ptr 需要保存 weak_ptr 的引用计数?

Why does shared_ptr needs to hold reference counting for weak_ptr?

引自 C++ 入门 $12.1.6:

A weak_ptr (Table 12.5) is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. Once the last shared_ptr pointing to the object goes away, the object itself will be deleted. That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.”

但是,我读过 article 说:

using make_shared is more efficient. The shared_ptr implementation has to maintain housekeeping information in a control block shared by all shared_ptrs and weak_ptrs referring to a given object. In particular, that housekeeping information has to include not just one but two reference counts:

  1. A “strong reference” count to track the number of shared_ptrs currently keeping the object alive. The shared object is destroyed (and possibly deallocated) when the last strong reference goes away.

  2. A “weak reference” count to track the number of weak_ptrs currently observing the object. The shared housekeeping control block is destroyed and deallocated (and the shared object is deallocated if it was not already) when the last weak reference goes away.

据我所知,make_shared创建的shared_ptr和那些refcontrol block是同一个countings.So对象,直到最后才释放weak_ptr 到期。

问题:

  1. 入门书错了吗? 因为 weak_ptr 实际上会影响该对象的生命周期。
  2. 为什么shared_ptr需要跟踪它的弱引用?weak_ptr可以通过检查控制块中的强引用来判断对象是否存在,所以我认为控制块不需要跟踪 weak refs
  3. 好奇一下,shared_ptr创建的控制块长什么样?是不是像:

    template<typename T>
    class control_block
    {
       T object;
       size_t strong_refs;
       size_t weak_refs;
       void incre();
       void decre();
       //other member functions...
    };
    //And in shared_ptr:
    template<typename T>
    class shared_ptr
    {
       control_block<T> block;//Is it like this?So that the object and refs are in the same block?
       //member functions...
    };
    

weak_ptr 需要指向可以判断对象是否存在的东西,以便它知道是否可以将其转换为 shared_ptr。因此需要一个小对象来管理这些信息。

当最后一个week_ptr(或shared_ptr)被移除时,需要销毁这个管家控制块。因此它必须同时记录 shared_ptr 和 week_ptr。

请注意,管理控制块与 ptr 指向的对象不同,因此 week_ptr 不会影响对象的生命周期。

有许多不同的方法可以实现智能指针,具体取决于您希望它具有的行为。如果您想了解更多,我会推荐 Alexandrescu (https://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315)

的 "Modern C++ Design"

引用计数控制 pointed-to-object 的生命周期。弱计数没有,但是确实控制(或参与控制)控制块.

的生命周期

如果引用计数达到 0,则对象 已销毁 ,但不一定 已释放 。当弱计数达到 0 时(或者当引用计数达到 0 时,如果没有 weak_ptr 发生这种情况),控制块被销毁并释放,并且如果对象的存储尚未释放,则将其释放。

destroyingdeallocating 之间的分离 pointed-to-object 是一个你不需要关心的实现细节,但它是由使用 make_shared.

引起的

如果你这样做

shared_ptr<int> myPtr(new int{10});

您为 int 分配存储空间,然后将其传递给 shared_ptr 构造函数,该构造函数分别为控制块分配存储空间。在这种情况下,可以尽早释放 int 的存储空间:一旦引用计数达到 0,即使仍然存在弱计数。

如果你这样做

auto myPtr = make_shared<int>(10);

然后 make_shared 可能会执行优化,它一次性为 int 和控制块分配存储空间。这意味着 int 的存储不能被释放,直到控制块的存储也可以被释放。 int 的生命周期在引用计数达到 0 时结束,但它的存储直到弱计数达到 0.

才被释放

现在清楚了吗?

weak_ptr和shared_ptr都指向包含控制块的内存。如果您在 shared_ptr 计数器达到 0 时立即删除控制块(但弱计数器没有),您将留下指向垃圾内存的 weak_ptrs。然后,当您尝试使用 weak_ptr 时,它会读取已释放的内存并发生错误 (UB)。

出于这个原因,只要 weak_ptr 可以尝试读取它,控制块就必须保持活动状态(分配和构造,而不是销毁或释放)。

一旦共享计数器达到 0,主 (pointed-to) 对象将被销毁并可能(希望)被释放。当两个计数器都达到 0 时,控制块将被销毁并被释放。

一个很好的第一步是在你对概念的心理表征中弄清楚 destructionde-allocation 之间的区别— 这也是比 is-an-implementation-detail-you-don't-need-to-care-about(友善地提到)、ignorance-reinforcing 步骤更可取的步骤。

因此,设 SeriousObjectclass,其大小约为系统内存的一半,并且在构建时控制鼠标,我们认为隐含的 side-effects 是 destructed,但不是 de-allocated本场景中 SeriousObject 实例 。在这种情况下,虽然鼠标控制又回来了,但您仍然只有一半的内存可用。最坏的情况是,代码中的某处被遗忘,甚至更糟的是泄露,weak_ptr 存在,让你的记忆变得蹩脚,情绪杀手和显示塞子 50% 的剩余部分其余的执行。但是,嘿,至少它没有泄露,对吧?

假设这里的结论是我对你的每个问题的假设:

  1. 实际上 不知道,我敢打赌,我一半的钱都花在作者身上 double-checked 文章发表之前,另一半花在错误上如果它本来就存在的话,现在肯定已经被发现了。
  2. 因为在那种情况下,weak_ptr 没有被跟踪,shared_ptr 和控制块都被破坏了,并且至少有一个指向 objectweak_ptr 存在,你怎么看当 weak_ptr 尝试按照您的建议检查控制块中的 strong refs 时,会发生什么? ...到处都是偏头痛。
  3. 这一次,肯定不知道,我只想说:你,还有我和许多其他 coding-related 人,忘记这个卑微的资源,这个呃..是的..还有这个——一方面——绝对不是没有用——另一方面——事情,这也发生了成为免费的 open-source、real-life-case、industry-level-quality 知识资源。兄弟们,加油!时机已到,抓住时机,对我们来说,以及血缘和定义,一直如此,永远不可侵犯,我们在出生时就获得了权利,我们通过荣誉获得奴役特权,并且我们能够和如此超越死亡责任!

PS(或者更像是顺便说一句,实际上)

我个人感到困惑的不是 weak_ptr 计数,而是在对象生命周期的特定阶段做出这样的优化的决定,我参考in-one-go-allocation-type-optimization并详细说明,我的意思是选择优化最短的可能single-time-occuring生命阶段,同时接受以如此技术和行为side-effects付出如此代价,作为交换,真正毫不费力地拿起一把山羊粪便作为他们劳动的收获。噗