问题
#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 ofT
.*this
shall be a subobject of an objectt
of typeT
. There shall be at least oneshared_ptr
instancep
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