Unexpected behavior from shared_from_this when creating multiple shared_ptr families on the same object

Here is a sample 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 inside enable_shared_from_this expires, so the next call to shared_from_this fails.

I expected nonDeletingSharedFoo to have a completely separate reference count from sharedFoo since it was created from a raw pointer, but obviously it still affects the weak count of the Foo internal weak_ptr . I assume this is because the shared_ptr constructor and / or destructor do something special when the pointer type implements enable_shared_from_this .

Does this code violate the standard? Is there a solution, or is it just impossible to have multiple shared_ptr "families" over an object that implements enable_shared_from_this ?

+5
source share
2 answers

Here you are in the gray area: enable_shared_from_this usually implemented using the shared_ptr constructors, which take responsibility for the raw pointer to the object obtained from enable_shared_from_this , set a weak_ptr contained within the object. Thus, later calls to shared_from_this() should return something. When you "reparent" sharedFoo , the original weak_ptr overwritten so that it contains the expired value when you finally call shared_from_this .

It is possible that this behavior is prohibited by the standard, but I think it is more likely that it is allowed, and that the semantics of ownership are slightly unproven in this admittedly niche corner business. The standard states that " shared_ptr constructors that create unique pointers can detect the existence of the enable_shared_from_this base and assign the newly created shared_ptr its __weak_this member __weak_this " ([Util.smartptr.enab] / 11). Despite the fact that the notes are not normative, I think that this indicates the intention of the standard.

You can avoid the problem altogether by creating a truly empty shared_ptr that has no ownership, but nonetheless points to sharedFoo :

 std::shared_ptr<Foo> nonDeletingSharedFoo(std::shared_ptr<Foo>(), sharedFoo.get()); 

This uses the aliasing constructor:

 template<class Y> shared_ptr(const shared_ptr<Y>& r, T* p) noexcept; 

which creates shared_ptr , which has ownership of r , in this case the empty shared_ptr and points to p . The result is an empty (non-owning) shared_ptr that points to the same object as sharedFoo .

You are responsible for ensuring that such a pointer that does not have ownership is not dereferenced after the expiration of the referent. It would probably be better to clear the design so that you really share the property or use a raw pointer instead of breaking the "non-owning shared_ptr".

+2
source

When you use get from sharedFoo ( sharedFoo.get () ), you get the address that is contained in shared_ptr. Thus, nonDeletingSharedFoo does not have access to shared_ptr, and when you reset nonDeletingSharedFoo, you free up address memory. Thus, the sharedFoo object does not exist now.

0
source

Source: https://habr.com/ru/post/1216104/


All Articles