简体   繁体   中英

Why does std::enable_shared_from_this allow multiple std::shared_ptr instances?

There are several questions that cover the behavior of std::enable_shared_from_this , but I don't think that this is a duplicate.

Classes that inherit from std::enable_shared_from_this carry a std::weak_ptr member. When the application creates a std::shared_ptr pointing to a subclass of std::enable_shared_from_this , the std::shared_ptr constructor checks the std::weak_ptr , and if it is not initialized, initializes it and uses the std::weak_ptr control block for the std::shared_ptr . However, if the std::weak_ptr is already initialized, the constructor just creates a new std::shared_ptr with a new control block. This sets the application up to crash when the reference count of one of the two std::shared_ptr instances goes to zero and deletes the underlying object.

struct C : std::enable_shared_from_this<C> {};

C *p = new C();
std::shared_ptr<C> p1(p);

// Okay, p1 and p2 both have ref count = 2
std::shared_ptr<C> p2 = p->shared_from_this();

// Bad: p3 has ref count 1, and C will be deleted twice
std::shared_ptr<C> p3(p);

My question is: why does the library behave this way? If the std::shared_ptr constructor knows that the object is a std::enable_shared_from_this subclass and bothers to check the std::weak_ptr field, why doesn't it always use same control block for the new std::shared_ptr , thus avoiding a potential crash?

And for that matter, why does the method shared_from_this fail when the std::weak_ptr member is not initialized, instead of just initializing it and returning a std::shared_ptr ?

It seems strange that the library works the way that it does, since it fails in situations when it could easily succeed. I'm wondering if there were design considerations/limitations that I don't understand.

I am using Clang 8.0.0 in C++17 mode.

If I understand your question correctly, you would assume that calling the constructor shared_ptr a second time would logically reuse the control block stored in shared_from_this.

From your point of view, this looks logical. Let's assume for a moment that C is part of a library your are maintaining and the usage of C is part of a user of your library.

struct C : std::enable_shared_from_this<C> {};

C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Valid given your assumption

Now, you found a way to no longer need the enable_shared_from_this and in the next version of your library, this gets updated to:

struct C {};

C *p = new C();
std::shared_ptr<C> p1(p);
std::shared_ptr<C> p3(p); // Now a bug

Suddenly, perfectly valid code becomes invalid without any compiler error/warning, because of upgrading your library. Where possible this should be prevented.

At the same time, it will cause a lot of confusion. Cause depending on the implementation of the class you put in shared_ptr, it's either defined or undefined behavior. It's less confusing to make it undefined every single time.

enable_shared_from_this is a standard workaround for getting a hold of a shared_ptr if you don't have a shared_ptr . A classic example:

 struct C : std::enable_shared_from_this<C>
 {
     auto func()
     {
         return std::thread{[c = this->shared_from_this()]{ /*Do something*/ }};
     }

     NonCopyable nc;
 };

Adding your mentioned extra functionality does add extra code whenever you don't need it, just for the check. Not that it would matter that much, though, zero overhead abstractions ain't nearly zero overhead abstractions.

This is not an answer to the question but more of a reply to user jvapen based on his answer to this question.

You had stated this in your answer:

 struct C {}; C *p = new C(); std::shared_ptr<C> p1(p); std::shared_ptr<C> p3(p); // Now a bug 

What I'm not seeing here is how line 5 std::shared_ptr<C> p3(p); is now a bug. According to cppreference:shared_ptr they specifically state this:

std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object.

Creating two shared_ptr s to the same pointer is undefined behaviour and is nothing to do with std::enable_shared_from_this . Your code should be:

struct C : std::enable_shared_from_this<C> {};

C *p = new C();
std::shared_ptr<C> p1(p);

std::shared_ptr<C> p2 = p->shared_from_this();

std::shared_ptr<C> p3(p1);

Creating a secondary smart pointer that is either actually non owning (it does nothing when the last copy is reset/destroyed), or carries a copy of the original smart pointer in the control block (in the deleter object) such that when the secondary reference count goes to zero the primary refcount is decremented, is a very rare occurrence and would probably be confusing for most programmers, but it isn't inherently illegal. (And I think one can make a strong case for that pattern in special cases.)

On the other hand, the existence shared_from_this strongly suggests that there is only one owning shared_ptr , and so having shared_from_this should probably be avoided when multiple sets of std::shared_ptr are expected. Explicit management of self referential non owning pointers is safer as it makes such issues evident in user code, unlike the implicit behavior of std::enable_shared_from_this .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM