【问题标题】:std::enable_shared_from_this: is it allowed to call shared_from_this() in destructor?std::enable_shared_from_this: 是否允许在析构函数中调用 shared_from_this()?
【发布时间】:2015-04-05 00:19:56
【问题描述】:
#include <memory>
#include <iostream>

struct A : public std::enable_shared_from_this<A>
{
    ~A()
    {
        auto this_ptr = shared_from_this(); // std::bad_weak_ptr exception here. 
        std::cout << "this: " << this_ptr;
    }
};

int main()
{
    auto a = std::make_shared<A>();
    a.reset();
    return 0;
}

我在调用shared_from_this() 时收到std::bad_weak_ptr 异常。是设计使然吗?是的,这可能很危险,因为在析构函数返回后无法使用此指针,但我看不出为什么在技术上不可能在此处获取指针,因为共享指针对象显然仍然存在并且可以用过的。除了写我自己的enable_shared_from_this 模拟(我宁愿不这样做)之外,有什么办法可以避免这种情况?

【问题讨论】:

  • @Drax:我已经看到了这个问题。它涉及boost 而不是std,答案讨论了相关代码的具体设计,而不是shared_from_this() 可用性的主要限制。
  • @VioletGiraffe 这个问题既不涉及boost 也不涉及std,只涉及弱引用的概念。

标签: c++ c++11 destructor shared-ptr weak-ptr


【解决方案1】:

我看不出为什么在技术上不可能在这里获取指针,因为共享指针对象显然仍然存在并且可以使用。

这是不可能的,有一个很好的技术原因。

shared_ptr 可能存在,但A 对象的引用计数已达到零,这就是运行析构函数的原因。一旦引用计数达到零,它就不能再次增加(否则您可能会得到一个shared_ptr,它引用了一个正在运行其析构函数或已被销毁的对象)。

调用shared_from_this() 会尝试增加引用计数并返回与当前所有者共享所有权的shared_ptr,但您不能将计数器从零增加到一,因此它失败了。

这个非常特殊的情况(在对象的析构函数中)你知道对象还没有被完全销毁,但是enable_shared_from_this&lt;A&gt; 无法知道是谁在调用shared_from_this() 函数,所以无法知道它是发生在这个非常特殊的情况中还是发生在对象的析构函数之外的其他一些代码中(例如,在另一个线程中,它将继续在析构函数之后运行)。

如果您能以某种方式使其适用于这种特定情况,并且您有一个 shared_ptr&lt;A&gt; 表示当前正在销毁的对象,您可以将 shared_ptr 提供给存储它以供以后使用的析构函数之外的东西。这将允许其他代码在对象被销毁后访问悬空的shared_ptr。这将是shared_ptrweak_ptr 类型系统中的一个大漏洞。

【讨论】:

    【解决方案2】:

    [util.smartptr.enab]/7 描述shared_from_this的前提条件:

    要求: enable_shared_from_this&lt;T&gt; 应该是 T 的可访问基类。 *this 应该是T 类型的对象t 的子对象。 至少应有一个shared_ptr 实例p 拥有&amp;t [emph.补充]

    由于您的对象正在被销毁,因此肯定没有拥有它的shared_ptr。因此,您不能在不违反该要求的情况下调用 shared_from_this,从而导致未定义的行为。

    【讨论】:

    • “既然你的对象正在被销毁,那么肯定没有shared_ptr拥有它。”除非有人疯狂到显式地调用析构函数;)
    • @T.C.如果是这样,那么“某人”显然认为 they 实际上是所有者,而不是引用该对象的 shared_ptr。我的论点是;)
    • 我的想法是,由于shared_ptr负责销毁一个对象,所以指针不会被销毁,直到它持有的对象被删除之后。但显然,引用计数器对象在此之前就被销毁了,至少在 MS 实现中,所以它在析构函数中已经不可用了。
    • @VioletGiraffe,不,引用计数器没有被破坏,但是计数已经减到零(否则对象不会被破坏,正如凯西所说),一旦达到零,它就不能再次增加。调用shared_from_this() 会尝试增加引用计数,但由于它已经为零,它会抛出bad_weak_ptr
    【解决方案3】:

    shared_ptr::reset 的实现通常是shared_ptr().swap(*this)

    这意味着您尝试复制的shared_ptr 已经处于其析构函数状态,这反过来又会在调用您的析构函数之前递减共享计数。当您调用 enable_shared_from_this 时,它将尝试通过从 weak_ptr 构造 shared_ptr 来提升存储在其中的 weak_ptr,这会在计数为 0 时导致异常。

    因此,要回答您的问题,如果您的标准库实现没有以授权的方式运行(我不知道它是否由标准规定),则没有标准的方式来做您想做的事情.

    现在,这是一个适用于我的机器的 hack (clang/libc++):

    #include <memory>
    #include <iostream>
    
    class   hack_tag
    {
    };
    
    namespace std
    {
    
      template<>
      class shared_ptr<hack_tag>
      {
      public:
        template<typename T>
        weak_ptr<T>        extract_weak(const enable_shared_from_this<T>& shared)
        {
          return shared.__weak_this_;
        }
      };
    
    };
    
    using weak_ptr_extractor = std::shared_ptr<hack_tag>;
    
    class   test : public std::enable_shared_from_this<test>
    {
    public:
      test()
      {
        std::cout << "ctor" << std::endl;
      }
    
      ~test()
      {
        std::cout << "dtor" << std::endl;
        weak_ptr_extractor  hacker;
        auto weak = hacker.extract_weak(*this);
        std::cout << weak.use_count() << std::endl;
        auto shared = weak.lock();
      }
    };
    
    
    int     main(void)
    {
      std::shared_ptr<test>  ptr = std::make_shared<test>();
    
      ptr.reset();
    }
    

    但我不确定您是否可以对此做任何有用的事情,因为您复制的 shared_ptr 即将死亡,并且该副本不会与您在 @987654331 之后获得的新 shared_ptr 共享内容@调用。

    【讨论】:

      【解决方案4】:

      你可以强制它被允许,但它有点“自爆”,我无法预测所有后果,但下一个代码按预期工作,允许在 dtors 中调用 shared_from_this() (你可以替换调用 boost也由 malloc/free):

      template<class GenT, typename... Args>
      struct AllocSharedObj
      {
          static std::shared_ptr<GenT> alloc(Args&&... args)
          {
      
              using pool_t = boost::singleton_pool<GenT, sizeof(GenT)>;
              void *mem = pool_t::malloc();
              //log_create_delete(true);
              auto r = std::shared_ptr<GenT>(new (mem) GenT(std::forward<Args>(args)...), [](GenT * p)
              {
                  if (p)
                  {
                      //log_create_delete(false);
      
                      //dirty hack, allowing to call SHARED_FROM_THIS inside that functions >:
                      auto cheat = std::shared_ptr<GenT>(p, [](auto) {});
                      p->~GenT();
                      cheat = nullptr;
      
                      pool_t::free(p);
                  }
              });
              //here can be post-constructor init which needs shared_from_this like
              r->init();
              return r;
          }
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-12
        • 2020-11-11
        • 1970-01-01
        • 2011-03-24
        • 1970-01-01
        • 2010-10-10
        相关资源
        最近更新 更多