【问题标题】:Why does enable_shared_from_this embed a weak pointer instead of embedding the reference counter directly?为什么 enable_shared_from_this 嵌入弱指针而不是直接嵌入引用计数器?
【发布时间】:2011-10-13 04:58:49
【问题描述】:

enable_shared_from_this 帮助器包含一个弱指针,该指针在创建指向对象的共享指针时设置。这意味着在对象中存在引用计数(使用make_shared 单独或与对象一起分配)和额外的weak_ptr

现在为什么不简单地包含引用计数呢?当从哑指针设置shared_ptr时,类型必须完全定义,所以shared_ptr构造函数或赋值运算符可以检测到类型是从enable_shared_from_this派生的并使用正确的计数器并且格式可以保持不变,所以复制不在乎。事实上,shared_ptr 已经检测到它来设置嵌入的weak_ptr

【问题讨论】:

    标签: c++ c++11 shared-ptr enable-shared-from-this


    【解决方案1】:

    首先想到的是这种方法是否可行,答案是不可行:

    struct X : enable_shared_from_this {};
    std::shared_ptr<X> p( new X );
    std::weak_ptr<X> w( p );
    p.reset();                      // this deletes the object
    if ( w.use_count() ) {          // this needs access to the count object
                                    //    but it is gone! Undefined Behavior
    

    如果计数存储在对象中,则没有weak_ptr 可以超过对象,这违反了合同。 weak_ptr 的整个想法是它们可以比对象更长寿(如果最后一个 shared_ptr 超出范围,即使有 weak_ptr,对象也会被删除)

    【讨论】:

    • 我原本以为弱指针可以形成一个链表并在对象被删除时被重置,但是以线程安全的方式这样做显然是一个巨大的蠕虫罐,它不是真的可行的。
    • @Jan Hudec:无论每个weak_ptr 存储在哪里,都有一段共享数据:对象是否存在,该数据在count 对象中,并且在最后一个 weak_ptr 超出范围/被删除之前无法删除。请注意,weak_ptr 在对象死亡时被释放,它们可以比指向对象的寿命更长(整个目的是它们可以)。我不认为多线程与这个问题有任何关系。
    • 是的,我意识到它们是这样实现的。线程的问题是:您可以实现弱指针,以便指向对象的所有弱指针都连接到链表,并且在对象被销毁时将它们全部归零,因此没有共享部分必须保留。然而,操作列表需要大量的锁定,这将使该方法在 C++ 中变得非常慢(虽然它可以在具有 stop-the-world 垃圾收集的环境中有效地使用,或者像 python 一样具有大解释器锁定的环境)。
    • 你可以保留数据结构,只销毁对象。不好的 sizeof 对象很大。您可以根据 sizeof 选择不同的实现。
    • @curiousguy:你的意思是引用计数将被一起分配,但不是对象的一部分。你不能真正用 基类 来做到这一点,要么引用计数是类的一部分,要么不是。因为 object 的销毁意味着基类的销毁,这意味着引用计数不能嵌入到enable_shared_from_this 中。另一方面,优点是连续分配,这已经可以通过使用make_shared 来实现(单个分配,两个对象:引用计数/真实对象)。
    【解决方案2】:

    关注点分离:make_shared 用于嵌入计数,enable_shared_from_this 用于shared_from_this

    没有理由将两者混为一谈:库不能假设客户端代码有什么需求。通过将两者分开,客户端代码可以挑选最适合的代码。

    此外 Boost(shared_ptr 来自哪里)还提出了intrusive_ptr

    (考虑到您的建议似乎不允许自定义删除器。您可以通过将enable_shared_from_this 更改为template&lt;typename T, typename Deleter = default_deleter&lt;T&gt;&gt; class enable_shared_from_this; 来解决此问题,但此时它已接近于重新发明intrusive_ptr .)

    【讨论】:

    • 如果enable_shared_from_this 嵌入了计数,它会节省空间,所以效率会更高。关注点分离不是以较低效率的方式实施它的理由。而且我认为它不会阻止自定义删除器以正常方式传递(shared_ptr 构造函数只需要稍微不同地包装它,因为它不应该删除引用计数)。
    • @Jan 你有没有考虑过删除器可能有状态并且有不同的大小?如果您执行shared_ptr&lt;enabled_type&gt;(new enabled_type, some_deleter()),您的方案完成了多少次分配?如果删除器是单独分配的,那么引用计数是嵌入到对象中还是与删除器一起存在真的很重要吗?
    • 嗯,你说得对,通用删除器的情况很复杂。不过,在使用默认删除器的常见情况下,它仍然可能更有效。
    • @Jan 这就是我首先提到关注点分离的原因:make_shared 确实设置了一些限制(即没有自定义删除器),这不是一种适用于每种情况的优化。就像你建议的那样。
    • @Luc Danton:我还没有阅读 shared_ptr 的实现,但我希望它在删除器上执行类型擦除,这意味着类型擦除的对象将是相同的计数对象。
    【解决方案3】:

    要从将计数嵌入对象中获得任何优势,您需要 取消shared_ptr 中的第二个指针,这将 改变它的布局,也给创建析构函数带来了问题 目的。如果更改布局,则此更改必须可见 在任何地方都使用shared_ptr。这意味着你不能拥有 shared_ptr 的实例指向不完整的类型。

    【讨论】:

    • 不,我不会。复制额外的指针很便宜。昂贵的是分配。
    • 你能解释一下这些问题吗?
    • @JanHudec 很大程度上取决于编译器;在极端情况下(但我不知道这样的编译器),带有单个指针的shared_ptr 可以在寄存器中传递,而带有两个指针的shared_ptr 则不会。这将产生根本性的不同。更一般地说,复制两个指针会比复制一个指针花费更多。但是你是对的,分配是昂贵的,另一个重要的问题是局部性——一个单独的计数器可能在不同的缓存行中,这会增加缓存命中。因此,即使使用第二个指针,您也可以获得很多。
    • @curiousguy 我不确定你在说哪些问题。如果您将计数器嵌入到对象中,则没有地方可以放置析构函数对象,而不是像boost::shared_ptr 那样分配单独的计数器析构函数。
    • 你会把完全相同的东西放在你单独分配的对象中,所以不,这不是一个论点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 2010-10-13
    • 2013-02-09
    • 1970-01-01
    • 2013-03-07
    相关资源
    最近更新 更多