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?
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.
As already mentioned by other users, calls to shared_from_this
on instances that are not owned by shared_ptr
s 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.
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