std :: enable_shared_from_this:是否允许在析构函数中调用shared_from_this()?

时间:2015-02-05 08:05:23

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

#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;
}

我在调用std::bad_weak_ptr时遇到shared_from_this()异常。它是按设计的吗?是的,它可能是危险的,因为在析构函数返回后不能使用此指针,但我没有看到为什么在技术上不可能在此处获取指针的原因,因为共享指针对象显然仍然存在并且可以是用过的。有没有办法绕过这个,没有写我自己的enable_shared_from_this模拟(我宁愿不这样做)?

4 个答案:

答案 0 :(得分:13)

  

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

这是一个非常好的技术原因,为什么它不可能。

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

调用shared_from_this()尝试增加引用计数并返回与当前所有者共享所有权的shared_ptr,但是您无法将计数器从0增加到1,所以它失败了。

这个非常具体的案例中(在对象的析构函数内)你知道对象还没有完全被破坏,但enable_shared_from_this<A>无法知道谁正在调用shared_from_this()函数,因此无法知道它是否发生在这个非常具体的案例或者对象之外的其他一些代码中?析构函数(例如,在析构函数之后继续运行的另一个线程)。

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

答案 1 :(得分:10)

[util.smartptr.enab] / 7描述了shared_from_this的先决条件:

  

要求: enable_shared_from_this<T>应为T的可访问基类。 *this应为t类型的对象T的子对象。 至少有一个shared_ptr实例p拥有&t [emph。加入]

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

答案 2 :(得分:0)

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即将死去,而且副本并没有与新的{{1你得到shared_ptr电话。

答案 3 :(得分:0)

您可以强制执行此操作,但这有点“自爆”,我无法预测其所有后果,但是下一个代码按预期运行,可以在dtor中调用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;
    }
};