When introduced to the std::shared_ptr, many people immediately want to produce a function that returns a std::shared_ptr to this.
//BAD! creates multiple control blocks to the same pointer std::shared_ptr<Thingy> get_a_thingy() { return this; } When the function above is used to generate a std::shared_ptr, each call will create a new control block. The first std::shared_ptr to go out of scope will destruct the object, leaving the other std::shared_ptr objects pointing to the deleted memory.
The C++ standard gets around this issue via the function shared_from_this, which safely creates shared pointers to this without duplicate control blocks.
In order to enable the shared_from_this function, your class must inherit from std::enable_shared_from_this<T>:
class Thingy : public std::enable_shared_from_this<Thingy> {...} Using the shared_from_this function, the correct example for returning a std::shared_ptr to our object would be:
//Requires a shared_ptr to be created before use std::shared_ptr<Thingy> get_a_thingy() { return shared_from_this(); } One caveat to the shared_from_this function: a std::shared_ptr object must be created before shared_from_this is used. The shared_from_this function searches for the existing control block rather than creating a new one.
The simplest way to control this is to make the constructor private. Objects will be created using a static factory function that passes arguments to the private constructor.
// factory function that perfect-forwards args // to a private ctor template<typename ... T> static std::shared_ptr<Thingy> create(T&& ... t) { return std::shared_ptr<Thingy>(new Thingy(std::forward<T>(t)...)); } Using std::forward in the factory allows you to have multiple constructor prototypes while using a single create function:
private: //We make constructor private - shared_from_this() breaks if a shared //ptr has never been created before. (It looks for the control block) Thingy() : name_("Nameless Thing") {} Thingy(std::string name) : name_(name) {} Now, the C++ core guidelines says we should use make_shared instead of new, so let’s try that:
// factory function that perfect-forwards args // to a private ctor - we use this to create objects template<typename... T> static std::shared_ptr<SharedThing> create(T&&... t) { return std::make_shared<SharedThing>(std::forward<T>(t)...); //return std::shared_ptr<SharedThing>(new SharedThing(std::forward<T>(t)...)); } Well, we get an error:
include/c++/v1/memory:4324:5: error: static_assert failed due to requirement 'is_constructible<SharedThing, char const (&)[8]>::value' "Can't construct object in make_shared" static_assert( is_constructible<_Tp, _Args...>::value, "Can't construct object in make_shared" ); and:
In file included from ../examples/cpp/shared_ptr.cpp:1: include/c++/v1/memory:2155:9: error: field of type 'SharedThing' has private constructor : __value_(_VSTD::forward<_Args>(_VSTD::get<_Indexes>(__args))...) {} What’s going on here? Looking at cppreference.com, we can find a note for make_shared:
std::shared_ptr(new T(args...))may call a non-public constructor ofTif executed in context where it is accessible, whilestd::make_sharedrequires public access to the selected constructor.”
So our private constructor is a problem, but I don’t really want to make the thing public. As a workaround, we can define another class derived from SharedThing which simply constructs a SharedThing, and use that with make_shared:
// factory function that perfect-forwards args // to a private ctor - we use this to create objects template<typename... T> static std::shared_ptr<SharedThing> create(T&&... t) { // C++ core guidelines say we shouldn't do this: //return std::shared_ptr<SharedThing>(new SharedThing(std::forward<T>(t)...)); // But this doesn't work because our constructor is private, and we want that // for use with enable_shared_from_this //return std::make_shared<SharedThing>(std::forward<T>(t)...); // So we'll make a "fake" derived class that constructs our shared thing // using a public constructor... struct EnableMakeShared : public SharedThing { EnableMakeShared(T&&... arg) : SharedThing(std::forward<T>(arg)...) {} }; // and use that type with make_shared return std::make_shared<EnableMakeShared>(std::forward<T>(t)...); } So now that we’ve set up our class to enable shared_from_this, how do we actually put everything together?
When creating the object, simply use the static factory function we defined:
auto thingy = Thingy::create("Thing 1"); Once the object is created, you can safely generate a std::shared_ptr:
auto t = thingy->get_a_thingy(); And that’s all there is to it!
Example Source Code
Further Reading
- C++ Smart Pointers
- Ditch Your C-Style Pointers for Smart Pointers
- C++ Smart Pointers with Aligned Malloc/Free
std::enable_shared_from_thisstd::enable_shared_from_this::shared_from_this
Using C++ Without the Heap
Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.
Learn More on the Course Page
