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

#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 get an exception std::bad_weak_ptr when calling shared_from_this() . Is it for design? Yes, this can be dangerous, since this pointer cannot be used after the destructor returns, but I see no 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 a way around this if I don't write my own analogue enable_shared_from_this (which I would rather not do)?

+7
c ++ c ++ 11 destructor shared-ptr weak-ptr
source share
3 answers

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

There is a very good technical reason why this is not possible.

shared_ptr may exist, but the reference count for object A reached zero, so the destructor starts. When the reference counter reaches zero, it cannot be incremented again (otherwise you can get shared_ptr , which refers to an object that is either in the middle of its destructor launch, or has already been destroyed).

The call to shared_from_this() tries to increase the reference counter and returns a shared_ptr , which shares ownership with the current owner (s), but you cannot increase the counter from zero to one, so it does not work.

In this very specific case (inside the object’s destructor), you know that the object has not yet been completely destroyed, but enable_shared_from_this<A> has no way of knowing who is calling the shared_from_this() function, so you cannot know if this is happening in this particular case or in some other piece of code outside the destructor object (for example, in another thread that will continue after the destructor).

If you could somehow make it work in this particular case, and you got shared_ptr<A> that referred to the object being destroyed, you could give this shared_ptr something outside the destructor that saved it for later use. This would allow another piece of code to access the tattered shared_ptr after the destruction of the object. This will be a big hole in a system like shared_ptr and weak_ptr .

+6
source share

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

Required: enable_shared_from_this<T> must be an available T base class. *this must be a subobject of an object T type T There must be at least one shared_ptr instance of p to which &t belongs. [emph. added]

Since your object is being destroyed, it should be such that shared_ptr does not belong to it. Therefore, you cannot call shared_from_this without violating this requirement, which will lead to undefined behavior.

+9
source share

shared_ptr::reset implementation often shared_ptr().swap(*this) .

This means that the shared_ptr that you are trying to copy is already in the destructor state, which in turn decreases the total counter until your destructor is called. When you call enable_shared_from_this , it will try to advance the weak_ptr stored in it by building shared_ptr from that weak_ptr , which will throw an exception when the counter is 0.

So, in order to answer your question, there is no standard way to do what you want if your standard library implementation does not behave in such a way as to allow it (I don't know if this conforms to the standard or not).

Now here is the 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 that you can do anything useful with this, since your owner of shared_ptr , which you copied, is about to die and this copy does not share things with the new clean shared_ptr that you receive after the reset call.

0
source share

All Articles