std::make_shared()、std::weak_ptr 和循环引用

std::make_shared(), std::weak_ptr and cyclic references

我的问题是关于这个声明:

If any std::weak_ptr references the control block created by std::make_shared after the lifetime of all shared owners ended, the memory occupied by T persists until all weak owners get destroyed as well, which may be undesirable if sizeof(T) is large. Source

我读到 ,该对象一直存在到最后一个 weak_ptr 出现。 它会释放使用 make_shared 创建的对象,并循环引用自身,还是会永远存在于内存中?

例如:

struct A
{
    std::weak_ptr<A> parent;
}

void fn()
{
    auto a=std::make_shared<A>();
    a->parent = a;
} // Will it destroy here or not?

它被摧毁了。这就是 weak_ptr 存在的原因之一。

a被销毁时,引用计数器变为0,所以对象被销毁。这意味着对象的析构函数被调用,它也破坏了 a->parent

不要混淆 销毁 释放。当引用计数器变为 0,或者没有 shared_ptr 拥有该对象时,该对象被 销毁 。如果有任何 weak_ptr 指向控制块,内存将不会被 释放 - 因为对象是用 std::make_shared 分配的 - 但对象肯定是摧毁.

问题涉及:

If any std::weak_ptr references the control block created by std::make_shared after the lifetime of all shared owners ended, the memory occupied by T persists until all weak owners get destroyed as well, which may be undesirable if sizeof(T) is large

类似于

std::weak_ptr<A> global;

void foo()
{
    auto a = std::make_shared<A>();
    global = a;
}

所以global指向a的控制块。 这里即使a被销毁,a使用的内存仍然存在,让控制块存在

通过实现指针存在一个对象可达性循环,也就是说,您可以跟随这些对象的私有实现内部使用的指针并返回到您开始的地方: a->parent 包含指向由 std::shared_ptrstd::make_shared 创建的元信息(控制块)的指针。

当然std::make_shared希望通过将元信息和托管对象放在一起来最大限度地减少动态内存分配的数量,但这与托管对象何时被销毁无关(这是唯一的可观察方面,因为没有使用 class 具体 operator new/operator delete)。因此,控制块是与托管对象并置,还是具有指向单独分配的对象的指针,都是无关紧要的。

除了少数退化的情况(智能指针是用一个不释放任何东西的假删除器构造的)之外的所有情况下,最后一个共享拥有智能指针的生命周期结束导致删除器运行,通常:

  • 的形式 delete p; 到 运行,其中 pstd::shared_ptr<U>、[=66= 的构造函数的类型 T* 的参数]
  • p->~T() 形式,其中 pstd::make_shared<T>()new (buff) T 的结果。

无论哪种情况,都可以从元信息中得到p的值。

[请注意,要删除的值 p 永远不会从存储在任何特定 std::shared_ptr<U> 实例中的 U* 指针值中获得,因为此类指针值可能不是 "deletable",因为析构函数可能不是虚拟的(只要指针参数 std::shared_ptr<U> 具有正确的静态类型:delete p; 就足够了,其中 p 是传递的类型的值到模板构造函数),因为指针可能指向成员子对象或完全不同的完整对象,如果别名构造函数用于构造另一个具有共享所有权的 std::shared_ptr。]

所以拥有一个指向控制块的指针通常允许一个人恢复一个指向受控对象的指针,尽管指向完整对象的指针不能通过 public 接口获得(除了指针被传递到删除器,所以在 C++ 中恢复指针的唯一方法,如果它丢失了,将传递一个自定义删除器并等待它被调用)。指针当然可以通过在内存表示中导航来恢复(尽管该导航可能需要在编译时使用 dynamic_cast 到未知类型,只要调试器知道所有派生的 classes).

所以我们有循环:

a
a->parent
   parent->control_block
           control_block.deleter (virtual call or stored function)
                         deleter.a

如果指针存储在动态创建的删除器中,这是创建 std::shared_ptr<U>(T*) 所必需的,或者

a
a->parent
   parent->control_block
           control_block.buffer

对于使用单个分配创建的对象 make_shared:对象是在该缓冲区内构造的,因此 &control_block.buffer == a.

但是指针的循环不是问题,只是所有权循环,因为它暗示 "self ownership controlled by lifetime",即 "I will destruct myself only when my lifetime will be over"(又名 "I will enter destructor when I will have entered destructor"),这是荒谬的。

这里没有所有权,因为弱引用只拥有元信息,不拥有信息