Here is some example code (online here):
#include <memory>
struct Foo : public std::enable_shared_from_this<Foo> {};
void example()
{
auto sharedFoo = std::make_shared<Foo>();
std::shared_ptr<Foo> nonDeletingSharedFoo(sharedFoo.get(), [](void*){});
nonDeletingSharedFoo.reset();
sharedFoo->shared_from_this(); // throws std::bad_weak_ptr
}
What I see (under multiple compilers) is that when nonDeletingSharedFoo
is reset, the weak_ptr
used internally by enable_shared_from_this
expires, so the subsequent call to shared_from_this
fails.
I expected nonDeletingSharedFoo
to have a completely separate ref count from sharedFoo
since it was constructed from a raw pointer, but obviously it is still affecting the weak count of the Foo
object's internal weak_ptr
. I assume this is because the shared_ptr
constructor and/or destructor do something special when the pointed-to type implements enable_shared_from_this
.
So does this code violate the standard? Is there a solution, or is it just not possible to have multiple shared_ptr
"families" over an object that implements enable_shared_from_this
?
You're in a gray area here: enable_shared_from_this
is typically implemented by having shared_ptr
constructors that take ownership of a raw pointer to an object derived from enable_shared_from_this
set a weak_ptr
contained inside the object. Thus later calls to shared_from_this()
have something to return. When you "reparent" sharedFoo
the original weak_ptr
value is being overwritten so that it contains an expired value when you finally call shared_from_this
.
It's possible that this behavior is forbidden by the standard, but I think it's more likely the intent that it is allowed and that the semantics of ownership are a bit underspecified in this admittedly niche corner case. The standard does note that "The shared_ptr
constructors that create unique pointers can detect the presence of an enable_shared_from_this
base and assign the newly created shared_ptr
to its __weak_this
member." ([util.smartptr.enab]/11). Despite the fact that notes are non-normative, I think it speaks to the intent of the standard.
You can avoid the problem altogether by creating a truly empty shared_ptr
that doesn't share ownership but nonetheless points at sharedFoo
:
std::shared_ptr<Foo> nonDeletingSharedFoo(std::shared_ptr<Foo>(), sharedFoo.get());
This takes advantage of the "aliasing" constructor:
template<class Y> shared_ptr(const shared_ptr<Y>& r, T* p) noexcept;
which creates a shared_ptr
that shares ownership with r
, in this case an empty default-constructed shared_ptr
, and points at p
. The result is an empty (non-owning) shared_ptr
that points at the same object as sharedFoo
.
You are responsible for ensuring that such a non-owning pointer is never dereferenced after the lifetime of the referent ends. It would probably be better to clean up the design so that you either truly share ownership, or use a raw pointer instead of the "non-owning shared_ptr" hack.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With