std::enable_shared_from_this: is it allowed to call shared_from_this() in destructor?

可紊 提交于 2019-12-30 08:08:48

问题


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

I'm getting std::bad_weak_ptr exception when calling shared_from_this(). Is it by design? Yes, it might be dangerous as this pointer can't be used after the destructor returns, but I don't see a reason why it would be technically impossible to get the pointer here, since the shared pointer object obviously still exists and can be used. Is there any way to circumvent this, short of writing my own enable_shared_from_this analog (which I would rather not do)?


回答1:


I don't see a reason why it would be technically impossible to get the pointer here, since the shared pointer object obviously still exists and can be used.

There's a very good technical reason why it's not possible.

The shared_ptr might exist, but the reference count for the A object has reached zero, that's why the destructor is being run. Once the reference count reaches zero it cannot be increased again (otherwise you could get a shared_ptr that refers to an object that is either in the middle of running its destructor, or has already been destroyed).

Calling shared_from_this() tries to increase the reference count and return a shared_ptr that shares ownership with the current owner(s), but you can't increase the counter from zero to one, so it fails.

In this very specific case (inside the object's destructor) you know the object hasn't been completely destroyed yet, but enable_shared_from_this<A> has no way to know who is calling the shared_from_this() function, so can't know if it's happening in this very specific case or in some other piece of code outside the object's destructor (e.g. in another thread that will keep going after the destructor).

If you could somehow make it work for this specific case and you got a shared_ptr<A> that referred to the object currently being destroyed, you could give that shared_ptr to something outside the destructor that stored it for later use. That would allow that other piece of code to access a dangling shared_ptr, after the object has been destroyed. That would be a big hole in the shared_ptr and weak_ptr type system.




回答2:


[util.smartptr.enab]/7 describes the preconditions for shared_from_this:

Requires: enable_shared_from_this<T> shall be an accessible base class of T. *this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t. [emph. added]

Since your object is being destroyed, it must be the case that there is no shared_ptr that owns it. Consequently, you cannot call shared_from_this without violating that requirement resulting in undefined behavior.




回答3:


shared_ptr::reset's implementation is often shared_ptr().swap(*this).

Which means the shared_ptr you are trying to copy is already in its destructor state which in turns decrement the shared count before calling your destructor. When you call enable_shared_from_this it will try to promote the weak_ptr stored within it by constructing a shared_ptr from that weak_ptr which results in an exception when the count is 0.

So to answer your question, there is no standard way of doing what you want if your standard library implementation doesn't behave in a way that authorise it (i don't know is it is mandated by the standard or not).

Now, here is a hack that works on my machine (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();
}

But i'm not sure you can do anything useful with that since your owning shared_ptr that you copied is about to die and that copy doesn't share things with the new clean shared_ptr you get after the reset call.




回答4:


You can force it be allowed, but it's kinda "headshoot urself", I can't predict all consequences of that, but next code works as expected, allows to call shared_from_this() in dtors (you can replace calls to boost by malloc/free too):

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


来源:https://stackoverflow.com/questions/28338978/stdenable-shared-from-this-is-it-allowed-to-call-shared-from-this-in-destru

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!