Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using shared_from_this() without managed shared pointer in C++11

Let's say I have a class which is a child class of enable_shared_from_this. The documentation of this base class says there should be a shared pointer which owns this class before calling shared_from_this. Is it safe to allocate the class with new and call shared_from_this to manage the object?

like image 856
Gustavo Avatar asked Dec 10 '15 22:12

Gustavo


2 Answers

From the standard:

§ 20.8.2.4

shared_ptr shared_from_this();

shared_ptr shared_from_this() const;

7 *Requires: enable_shared_from_this shall be an accessible base class of T. this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t.

8 Returns: A shared_ptr object r that shares ownership with p.

9 Postconditions: r.get() == this

If you call shared_from_this() within a class that is not managed by a shared_ptr the result will be undefined behaviour because you have not fulfilled one of the documented preconditions of the method.

I know from experience that in [the current version of] libc++ the result is an exception being thrown. However, like all undefined behavior this must not be relied upon.

like image 66
Richard Hodges Avatar answered Oct 06 '22 16:10

Richard Hodges


As already mentioned by other users, calls to shared_from_this on instances that are not owned by shared_ptrs will result in an undefined behavior (usually an exception, but there are no guarantees).

So, why one more answer?

Because I did myself the same question once and got almost the same answer, then I started struggling with another question that immediately arose after that - how can I guarantee thus that all the instances are managed by a shared_ptr?

For the sake of completeness, I add another answer with a few details about this aspect.
Here a simple solution that had not been mentioned before.

So simple a solution, indeed: private constructors, factory method and variadic templates.
It follows a snippet that mixes all of them together in a minimal example:

#include<memory>
#include<utility>

class C: public std::enable_shared_from_this<C> {
    C() = default;
    C(const C &) = default;
    C(C &&) = default;
    C& operator=(const C &) = default;
    C& operator=(C &&c) = default;

public:
    template<typename... Args>
    static std::shared_ptr<C> create(Args&&... args) noexcept {
        return std::shared_ptr<C>{new C{std::forward<Args>(args)...}};
    }

    std::shared_ptr<C> ptr() noexcept {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<C> c1 = C::create();
    std::shared_ptr<C> c2 = C::create(*c1);
    std::shared_ptr<C> c3 = c2->ptr();
    // these won't work anymore...
    // C c4{};
    // std::shared_ptr<C> c5 = std::make_shared<C>();
    // std::shared_ptr<C> c6{new C{}};
    // C c7{*c1};
    // ... and so on ...
}

The basic (trivial?) idea is to forbid the explicit construction of new instances, but by using the factory method here called create.
Variadic templates are used to avoid writing several factory methods, nothing more. Perfect forwarding helps us to do that the right way.

Pretty simple, isn't it?
Anyway it took me a while to figure out that, so I hope this will help future readers once across the same doubt.

like image 26
skypjack Avatar answered Oct 06 '22 16:10

skypjack