Improving on the best example on cppreference.com
TL;DR
In my opinion, the std::enable_shared_from_this page on cppreference.com has the best example code on the site, to explain what it does. This article is talking about an improvement that can be made to it using C++23’s Explicit object member functions, better known as deducing this. Here’s the godbolt link to the final code!
The best example on cppreference.com
Throughout my many visits to cppreference.com, I have found that most of the documented features of the C++ language and standard library are accompanied by great examples to explain what they do. However, there is one standard library facility that has a cppref page with an example that always sticks in my head as going above and beyond to explain what it does. And that page is std::enable_shared_from_this.
Why is this the “best” example?
Note
Other than the fact that the example literally has a class called Best in it of course!
The element of this example that sets it above the rest, in my opinion, is the fact that it shows you how not to use the facility. This is something that I think a lot of documentation fails to do. By talking about what not to do with the feature, it preempts users from thinking about using it in the incorrect way, and tells them why it is incorrect. This is an element that is often missed from documentation.
std::unique_ptr gets quite close to rivalling std::enable_shared_from_this for the title of “best example”. The example shows users how to make use of std::unique_ptr in the following ways:
- How to move ownership of a
std::unique_ptr - How it can be used with runtime polymorphism
- How to use the custom deleter
- How to manage dynamic arrays with it
- How to write a singly linked list with it
This is all brilliant. Especially the example with the linked list because it highlights a particularly nasty gotcha. The linked list part of the example gets close to showing users what not to do. However, the “what not to do” part of it is buried in a comment above some code showing you “what to do”.
~List()
{
// destroy list nodes sequentially in a loop, the default destructor
// would have invoked its “next”'s destructor recursively, which would
// cause stack overflow for sufficiently large lists.
while (head)
{
auto next = std::move(head->next);
head = std::move(next);
}
}And the issue with comments is that we aren’t always the best at reading them when we are in a hurry… The other issue I have with the example for std::unique_ptr is that it does not demonstrate the most obvious pitfall of any smart pointer:
Caution
Taking ownership of a raw pointer that is managed by someone else.
This is quite a common mistake that people learning about smart pointers often make. e.g.
// allocate int with new
int* heapInt = new int{42};
{
// make a unique_ptr to manage it
std::unique_ptr<int> managedInt{heapInt};
}
// ... then somewhere else someone tries to delete the original allocation
// But the unique_ptr has already deleted it when it went out of scope
// So this is a double delete!
delete heapInt;Let’s now go through the std::enable_shared_from_this example. The example goes through 3 uses in the following order:
- Good -> A good way to use the feature with caveats
- Best -> The best way to use the feature
- Bad -> A problem that
std::enable_shared_from_thissolves
I will switch the order up a little in my discussion like so:
- Bad
- Good
- Best
Tip
Hey, that’s a rotate!
Bad
The explicit bad code that is demonstrated in the example, shows us the problem that std::enable_shared_from_this solves.
struct Bad
{
std::shared_ptr<Bad> getptr()
{
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
void testBad()
{
// Bad, each shared_ptr thinks it is the only owner of the object
std::shared_ptr<Bad> bad0 = std::make_shared<Bad>();
std::shared_ptr<Bad> bad1 = bad0->getptr();
std::cout << "bad1.use_count() = " << bad1.use_count() << '\n';
} // UB: double-delete of Bad
The main thing I want to point out here, is that the “bad thing” is not just written about in a comment. It is written as actual code and given names that explicitly tell the user that this is bad. The struct is called Bad. The test function is called testBad. The variables are called bad0 and bad1. It is leaving nothing unambiguous here.
Important
This is bad. Don’t do this.
We don’t have to go reading comments to understand this. The code tells us. And even better we can just copy and paste it into godbolt and see and play with the bad thing without having to write it ourself. This is all great!
Good
This part of the example shows the basic use of std::enable_shared_from_this which prevents the double delete situation from the Bad example.
class Good
: public std::enable_shared_from_this<Good>
{
public:
std::shared_ptr<Good> getptr()
{
return shared_from_this();
}
};
void testGood()
{
// Good: the two shared_ptr's share the same object
std::shared_ptr<Good> good0 = std::make_shared<Good>();
std::shared_ptr<Good> good1 = good0->getptr();
std::cout << "good1.use_count() = " << good1.use_count() << '\n';
}So the idea is that we are using CRTP to make shared_from_this be a public member function that will return a std::shared_ptr that shares ownership of this with all the existing std::shared_ptrs that refer to this. The key thing is that we aren’t creating a whole new std::shared_ptr to this that would result in a double delete.
This is awesome and shows how std::enable_shared_from_this solves the double delete problem. Great! But the example goes on to show how even this can lead to misuse:
void misuseGood()
{
// Bad: shared_from_this is called without having std::shared_ptr owning the caller
try
{
Good not_so_good;
std::shared_ptr<Good> gp1 = not_so_good.getptr();
}
catch (std::bad_weak_ptr& e)
{
// undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
std::cout << e.what() << '\n';
}
}This function highlights the major downside to std::enable_shared_from_this. ANY object created from a class that inherits from std::enable_shared_from_this must be created and managed by a std::shared_ptr. This is not something that is immediately obvious to users of a class, especially a class in an inheritance hierarchy. It is a real issue that if you force your users to have to follow the inheritance chain of classes to the base class to figure out if std::enable_shared_from_this features anywhere in that hierarchy to figure out how you should create an object of that class. So simply using std::enable_shared_from_this in this way can remove local reasoning from your class, and makes even the simple decision on how to create an object complicated.
Tip
Since there is such a hard restriction on how we construct objects that have std::enable_shared_from_this, should we even support copying? These objects will always have reference semantics due to them always having to be managed by a std::shared_ptr. They are not things that can ever be created on the stack and just copied around like you would a std::string or an int.
This part in particular is what sets this example above the rest. This is example code, showing us what not to do and what happens when you do it.
Best
Luckily for us, the example keeps on knocking it out of the park and gives us a way to solve this problem!
class Best
: public std::enable_shared_from_this<Best>
{
struct Private{ explicit Private() = default; };
public:
// Constructor is only usable by this class
Best(Private) {}
// Everyone else has to use this factory function
// Hence all Best objects will be contained in shared_ptr
static std::shared_ptr<Best> create()
{
return std::make_shared<Best>(Private());
}
std::shared_ptr<Best> getptr()
{
return shared_from_this();
}
};
void testBest()
{
// Best: Same but cannot stack-allocate it:
std::shared_ptr<Best> best0 = Best::create();
std::shared_ptr<Best> best1 = best0->getptr();
std::cout << "best1.use_count() = " << best1.use_count() << '\n';
// Best stackBest; // <- Will not compile because Best::Best() is private.
}Best solves the problem above by making it actually impossible to call the class’ constructor. Instead, we must use the factory function Best::create to construct a Best. This means that we can mandate that all Bests are constructed as a std::shared_ptr.
It is a mild inconvenience that we can’t construct Bests with constructors, but it isn’t that much of an issue. And in fact, it is a common technique.
- In Unreal Engine,
UObjects can only be created with theNewObjectfactory function. - Also in Unreal Engine,
FSceneViewExtensions must useFSceneViewExtensions::NewExtensionto construct one. std::make_sharedis a factory function forstd::shared_ptrs that allowsstd::shared_ptrs to be constructed with 1 allocation rather than 2.
Summarizing the example
Before moving on, I just want to list out a summary of what this example does right:
- It describes what not to do with the feature
- It describes what happens when you do the bad thing
- It shows you a solution to prevent the bad thing from happening.
- Classes, functions, and variables are all given exceptionally clear names that describe the point that they are trying to get across.
The Passkey Idiom
The method in which Best makes it impossible to directly use it’s constructor is a variation of the pass key idiom, which is also worth talking about now.
Put simply, the passkey idiom is a way of enforcing certain functions to only be called by objects of specific classes or by specific functions. The link above is a fantastic article on the subject. But if you would prefer a video, Jody Hagins had a great lightning talk at CppNow in 2021 called One Friend Ain’t Enough.
As explained in the article and video linked above the usual form of the passkey idiom looks like this:
class Passkey
{
Passkey() = default;
friend struct AllowedClass;
friend void AllowedFunction();
};
void DoSecretThing(Passkey);
struct AllowedClass
{
void Execute()
{
DoSecretThing(Passkey{});
}
};
void AllowedFunction()
{
DoSecretThing(Passkey{});
}So we have some Passkey class that has private constructor, and a list of friends. This list of friends are the entities that are able to access Passkey’s constructor, and therefore are the only entities that are able to create a Passkey. This allows a function to be public while only allowing a very specific set of entities to actually call this function.
In the Best example above, a version of this is used with the Private struct. Private is a class that only Best can create objects of. This is due to the fact that Private is a private nested class of Best. The constructor of Best requires an object of type Private as an argument. So to be able to call that constructor we need to somehow get an object of type Private, and as we know, only Best can create Private objects. Therefore only member functions of Best such as the static member function, Best::create, can call the constructor. And so we can completely control how Best objects are created.
Writing the Bestest(?) class
Up till now, I have been gushing over how great this example is. But, there is a use of std::enable_shared_from_this that could be added to the example that is better than the shown Best class which solves yet another issue.
I hope it is quite clear by going through the examples of std::enable_shared_from_this that it is a massive sledgehammer of a tool, that also has some sharp edges. Kinda like this weapon from Final Fantasy

Once you add std::enable_shared_from_this to your inheritance hierarchy, you are putting heavy constraints on how you create, and manage the lifetime of objects of all classes in that hierarchy. And as the Best example showed, there is a decent amount of work you have to go through to make the class using std::enable_shared_from_this be safe, and easy to use correctly.
With this in mind, how about we try and remove all of that extra boilerplate that users of std::enable_shared_from_this must write to make their classes safe!
Lets start off with some code:
class Object
: public std::enable_shared_from_this<Object>
{
protected:
struct ObjectToken{ explicit ObjectToken() = default; };
public:
Object() = delete;
explicit Object(ObjectToken) {};
Object(const Object&) = delete;
Object(Object&&) = delete;
Object& operator=(const Object&) = delete;
Object& operator=(Object&&) = delete;
virtual ~Object() = default;
template<typename T, typename... ParamTypes>
static std::shared_ptr<T> New(ParamTypes&&... InArgs)
{
return std::make_shared<T>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}
};I have called this class Object because of the restrictions that std::enabled_shard_from_this puts on objects of classes that use it, as well as the capabilities that it bestows.
- They must be constructed using
std::make_shared - They probably should not be copied
- They can form relationships with
shared_from_this
These are all hallmarks for “object-y” types, as opposed to value types.
Tip
Very rarely do you reach for a std::shared_ptr<std::string> or std::shared_ptr<std::pair<int, double>>. But it is very possible that you would reach for a std::shared_ptr<Sword> where the inheritance hierarchy is Sword -> Weapon -> WieldableItem -> GameObject
The idea here is that you could have this class be the base class for all of your big object oriented classes and get all that great behaviour without worrying about it.
Note
This is probably looking quite familiar. Java and C# have their base Object classes. Unreal Engine has its base UObject class. This is not a new concept
The problem
Consider the following code:
class DerivedObject
: public Object
{
public:
DerivedObject(ObjectToken InPasskey)
: Object(InPasskey)
{
}
std::shared_ptr<DerivedObject> GetSharedFromThis()
{
// compiler error because shared_from_this returns a std::shared_ptr<Object>
return shared_from_this();
}
};shared_from_this returns a std::shared_ptr<Object>, so we have to provide another function that casts the result of shared_from_this to the derived class. Like so:
class DerivedObject
: public Object
{
public:
DerivedObject(ObjectToken InPasskey)
: Object(InPasskey)
{
}
std::shared_ptr<DerivedObject> GetSharedFromThis()
{
return std::static_pointer_cast<DerivedObject>(shared_from_this());
}
};Some might think the cast is the problem here, but I don’t think it is. The cost of the cast is fine in this context. The purpose of shared_from_this is to form relationships between heavy object oriented classes. This is not meant to be used in a performance hot spot.
My problem here is that it is not exactly convenient since it forces all derivations to provide this cast themselves. This isn’t great. Some might be tempted to then force the users to do the cast explicitly. This is just a terrible API design though. No one wants to see std::static_pointer_cast littered throughout their code base.
The issue is that having std::enable_shared_from_this at the top of this inheritance hierarchy adds a decent amount of boilerplate to every single class that wants to derive from it. Either that or force users of the classes to do their own casting.
So what can we do?
A bad solution
Notice that std::enable_shared_from_this uses CRTP which allows it to return a std::shared_ptr<T> where T is the derived class. Well couldn’t we just send the most derived type up the class hierarchy? And the answer is yes… kinda. Take the following code for starters:
template<typename Derived>
class Object
: public std::enable_shared_from_this<Object>
{
protected:
struct ObjectToken{ explicit ObjectToken() = default; };
public:
Object() = delete;
explicit Object(ObjectToken) {};
Object(const Object&) = delete;
Object(Object&&) = delete;
Object& operator=(const Object&) = delete;
Object& operator=(Object&&) = delete;
virtual ~Object() = default;
template<typename... ParamTypes>
static std::shared_ptr<Derived> New(ParamTypes&&... InArgs)
{
return std::make_shared<Derived>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}
std::shared_ptr<Derived> SharedFromThis()
{
// this de-reference necessary due to two phase name lookup
return this->shared_from_this();
}
};
class DerivedObject
: public Object<DerivedObject>
{
public:
DerivedObject(Object<DerivedObject>::ObjectToken In)
: Object<DerivedObject>(In)
{
}
};All of the issues with this code stem from the fact that Object is now a template.
The first major issue is that we can not derive any classes from DerivedObject because there is no way to both spell out the constructor AND have a working New function. For example if we have the following constructor:
class MoreDerivedObject
: public DerivedObject
{
public:
MoreDerivedObject(Object<MoreDerivedObject>::ObjectToken In)
: DerivedObject(In)
{
}
};the program will not compile because Object<MoreDerivedObject>::ObjectToken is not visible, because Object<MoreDerivedObject> is not a base of MoreDerivedObject. And since that is a base, we do not have access to the ObjectToken struct of Object. Object<DerivedObject> is a base though… So what happens if we use that instead?
class MoreDerivedObject
: public DerivedObject
{
public:
MoreDerivedObject(Object<DerivedObject>::ObjectToken In)
: DerivedObject(In)
{
}
};This appears to work… until we try to instantiate one.
int main()
{
auto test = Object<MoreDerivedObject>::New();
}We are back to conflicting Private instantiations. New is using an Object<MoreDerivedObject>::Private but the constructor is wanting an Object<DerivedObject>::Private. These are not related types in the slightest and so we get a compilation error.
We could soldier on and try and get some solution that fits, but there is yet another major issue with this solution, and that is of validation. We want to constrain the types passed to Object to being types that inherit from Object. That is how CRTP works and we should enforce it. There are two ways we can do this:
- Concepts
static_assert
But both have the same issue:
template<typename Derived>
class Object
: public std::enable_shared_from_this<Derived>
{
static_assert(std::derived_from<Derived, Object>);
// everything else...
};This won’t work because Derived is an incomplete class at this point of instantiation. We can use a workaround by putting the static_assert in the constructor (or some other member function). BUT this will mean that it would be possible for people to write their classes and only find out if it is invalid or not when they decide to instantiate it. Which ain’t great. I would like my validation done at the point of instantiation
Note
Roger Booth has a great article on a similar problem called Using the CRTP and C++20 Concepts to Enforce Contracts for Static Polymorphism.
As I said above, the fundamental problem is Object being a template. We need a way to get type information from the derived type to the base, without making the base a template… i.e. we want all of the utility of CRTP without actually using CRTP.
Enter Deducing This
Thanks to C++23 we can get what we want. Explicit object member functions, better known as deducing this provides us with the ability to pass the this object explicitly in member functions.
Tip
“Deducing this” essentially removes the use-case for CRTP, but it can also be used to wrap classes that already make use of CRTP to improve the API.
With deducing this, we can write SharedFromThis like so:
template<typename ThisType>
auto SharedFromThis(this ThisType&& InThis)
{
return std::static_pointer_cast<std::remove_reference_t<ThisType>>(std::forward<ThisType>(InThis).shared_from_this());
}For people who have not seen deducing this before, I highly recommend Sy’s article on it linked above and here. I will attempt a TL;DR of what is going on here though.
The idea is that we define the type of this that is passed in as the first parameter of the member function. Since this could be anything that derives from Object, we accept the type via template argument. We accept the this object by forwarding/universal reference, and then std::forward the object on to cope with the fact that this could be const or not const. Thanks to passing by forwarding/universal reference, we have to use std::remove_reference_t to get the correct type to give to static_pointer_cast. The reason we are returning with auto and not with decltype(auto) here is because shared_from_this will always return a value, never a reference.
I will admit that this does look more difficult than our first proposed solution, and perhaps to some, it doesn’t provide enough value to warrant the additional difficulty.
Note
I am purposely using the word “difficult” to describe this code, rather than “complex”. The reason for this is that we aren’t introducing any new entities to the function, or things that we need to track. What we are doing, is specifying the type of the this pointer explicitly (previously the this pointer existed implicitly), and in a generic way. For most people this is not easy. It is not close-to-hand knowledge. It is difficult. However, for people who have been using “deducing this” and are used to it, this code doesn’t look that difficult.
With that being said, I believe that it makes calling code simpler to write:
SharedFromThis<DerivedObject>();vs
SharedFromThis();Note
I am purposely using the word “simpler” here rather than “easier”. Yes it is technically also easier to write but that isn’t the primary intent, just a consequence. The main intent, is that the caller doesn’t have to think about the type to pass in. There are 1 fewer elements involved in the calling of the “deducing this” solution. Think about it from a code review perspective. Without deducing this, to verify if the call is correct or not, you would have to scroll up to see if the type passed to SharedFromThis is indeed the type of the class that we are in. With deducing this, this is not required. All the information required to validate the call is at the call-site. It is simpler.
With this added our Object class is now looking like this:
class Object
: std::enable_shared_from_this<Object>
{
protected:
struct ObjectToken{ explicit ObjectToken() = default; };
public:
Object() = delete;
explicit Object(ObjectToken) {};
Object(const Object&) = delete;
Object(Object&&) = delete;
Object& operator=(const Object&) = delete;
Object& operator=(Object&&) = delete;
virtual ~Object() = default;
template<typename T, typename... ParamTypes>
static std::shared_ptr<T> New(ParamTypes&&... InArgs)
{
return std::make_shared<T>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}
protected:
template<typename ThisType>
auto SharedFromThis(this ThisType&& InThis)
{
return std::static_pointer_cast<std::remove_reference_t<ThisType>>(std::forward<ThisType>(InThis).shared_from_this());
}
};I have made SharedFromThis protected mainly because it should only be derived classes that need to call it. Calling SharedFromThis from code outside of derived class member functions would imply that you already have an object that is of a class derived from Object. And the only way you could have one of those is if it is owned by a std::shared_ptr.
Note
I also have made std::enable_shared_from_this be inherited privately. Since we have SharedFromThis we no longer need to keep std::enable_shared_from_this as a public part of the API. It can be moved to a private implementation detail.
One counter argument to keeping SharedFromThis protected would be the following code:
void Set(const TestObject& InObject)
{
// Won't compile. No access to SharedFromThis :(
myObject = InObject.SharedFromThis();
}Here we have a function that takes in an object, which is of a type that is derived from Object, by const& and then wants to get a shared_ptr from it in order to create a new owner of the object. And this would be fine, and we might want to allow this, in which case we would just make SharedFromThis public. BUT, I have an issue with this. If I pass something by const&, I am doing so explicitly to not involve ownership semantics. I expect that function to use the Object and do nothing more. If I wanted to declare my intent for Set to take ownership of the TestObject the function should accept a std::shared_ptr<TestObject> instead.
Being explicit about our pre-conditions
Our Object class is now looking fantastic. But we can improve the API just a little by being up front with what we expect to be a valid call to New. This where C++20 concepts can come in handy:
template<typename T, typename... Params>
concept ObjectType =
std::derived_from<T, Object> &&
std::constructible_from<T, ObjectToken, Params...> &&
!std::same_as<T, Object>;
So here we are defining a concept called ObjectType which has 3 conditions:
- The type we want to construct is derived from
Object - The type is constructible from an
ObjectTokenand then 0 or moreParams - We are not constructing an instance of
Objectitself
And we can apply this concept as follows:
template<typename T, typename... ParamTypes>
requires ObjectType<T, ParamTypes...>
static std::shared_ptr<T> New(ParamTypes&&... InArgs)
{
return std::make_shared<T>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}In order to write the second condition in our ObjectType concept, we need to pull ObjectToken out of the Object class:
class ObjectToken
{
friend class Object;
ObjectToken() = default;
};
class Object;
template<typename T, typename... Params>
concept ObjectType =
std::derived_from<T, Object> &&
std::constructible_from<T, ObjectToken, Params...> &&
!std::same_as<T, Object>;
class Object : std::enable_shared_from_this<Object>
{
// ...
};This is not a bad thing. Our ObjectToken class has the exact same behaviour as it did before, and in ways this is better because we can now refer to it from code outside of the Object hierarchy.
Tip
We should not fight to make functionality a private implementation detail if it starts to not make sense as a piece of software grows.
Writing Tests
Before we go into writing tests lets just have a look at the final product that we are testing
class ObjectToken
{
friend class Object;
ObjectToken() = default;
};
class Object;
template<typename T, typename... Params>
concept ObjectType =
std::derived_from<T, Object> &&
std::constructible_from<T, ObjectToken, Params...> &&
!std::same_as<T, Object>;
class Object
: std::enable_shared_from_this<Object>
{
public:
Object() = delete;
explicit Object(ObjectToken) {};
Object(const Object&) = delete;
Object(Object&&) = delete;
Object& operator=(const Object&) = delete;
Object& operator=(Object&&) = delete;
virtual ~Object() = default;
template<typename T, typename... ParamTypes>
requires ObjectType<T, ParamTypes...>
static SharedPtr<T> New(ParamTypes&&... InArgs)
{
return MakeShared<T>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}
protected:
template<typename ThisType>
auto SharedFromThis(this ThisType&& InThis)
{
return std::static_pointer_cast<std::remove_reference_t<ThisType>>(std::forward<ThisType>(InThis).shared_from_this());
}
};You might be thinking, “there ain’t much runtime behaviour to test here”, and you would be entirely right! However, there is a lot of compile time behaviour to validate in all of this. The entire reason that we have written this class is to make an API for std::enable_shared_from_this that is:
easy to use correctly, and hard/impossible to use incorrectly
We can test to make sure that this is the case with some compile time tests.
ObjectType tests
The first tests that we are going to write are tests for the ObjectType concept. We want to make sure that it is actually making sure that it will correctly identify types as being valid Objects
struct NotInheritedFromObject {};
struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken) {} };
struct PrivateInherit : private Object { PrivateInherit(ObjectToken In) : Object(In) {} };
struct PublicInheritTokenSecondParam : public Object { PublicInheritTokenSecondParam(int, ObjectToken In) : Object(In) {} };
struct PublicInherit : public Object { PublicInherit(ObjectToken In) : Object(In) {} };
static_assert(!ObjectType<Object>, "Expected Object itself to not pass the ObjectType concept");
static_assert(!ObjectType<NotInheritedFromObject>, "Expected a class that does not inherit from Object to not pass the ObjectType concept");
static_assert(!ObjectType<NotInheritedFromObjectTakesObjectToken>, "Expected a class that does not inherit from Object but takes an ObjectToken to construct to not pass the ObjectType concept");
static_assert(!ObjectType<PrivateInherit>, "Expected Private inheritance to not pass the ObjectType concept");
static_assert(!ObjectType<PublicInheritTokenSecondParam, int>, "Expected accepting ObjectToken as the second param to not pass the ObjectType concept");
static_assert(ObjectType<PublicInherit>, "Expected Public inheritance to pass the ObjectType concept");The easiest way to test concepts is to create some test classes that represent your test cases, then evaluate them in a static_assert. Since concepts effectively boil down to compile time predicates, simply evaluating them returns either true or false.
Object::New tests
In a way, these are integration tests. We are testing that Object::New is correctly applying the restrictions that we would expect from ObjectType while also insuring that Object::New is returning what we expect: A std::shared_ptr<T>.
template<typename T>
concept TestConstructibleWithObjectNew = requires()
{
{ Object::New<T>() } -> std::same_as<std::shared_ptr<T>>;
};
static_assert(!TestConstructibleWithObjectNew<Object>, "Expected Object itself to not be constructible using Object::New");
static_assert(!TestConstructibleWithObjectNew<NotInheritedFromObject>, "Expected a class that does not inherit from Object to not be constructible using Object::New");
static_assert(!TestConstructibleWithObjectNew<NotInheritedFromObjectTakesObjectToken>, "Expected a class that does not inherit from Object but takes an ObjectToken to construct to not be constructible using Object::New");
static_assert(!TestConstructibleWithObjectNew<PrivateInherit>, "Expected Private inheritance to not be constructible using Object::New");
static_assert(TestConstructibleWithObjectNew<PublicInherit>, "Expected Public inheritance to be constructible using Object::New");As you can see these are very similar to the ObjectType tests. Some might even say they are needlessly redundant, but I feel like they serve a purpose: They protect ObjectNew from refactors that might change the constraints on the function.
Here we create a concept which acts as our test driver, then apply that concept with static_assert to test each scenario of calling Object::New.
Object::SharedFromThis Tests
The next major piece of logic that we want to test is the SharedFromThis function. The issue is that we purposefully made this protected, so we can’t test it directly. It can often be tempting to make certain implementation details public for the sake of tests but often, this is not necessary.
Tip
If you do find it necessary to make private implementation details public for the sake of tests, it can often be a smell that you need to divide your class up into smaller units to test individually.
In our case, we just need to create a derived Object class and use that to get to the functionality that we want to test.
struct TestObject : Object
{
TestObject(ObjectToken InToken) : Object(InToken) {}
template<typename ThisType>
decltype(auto) Get(this ThisType&& InThis)
{
return std::forward<ThisType>(InThis).SharedFromThis();
}
};
static_assert(std::same_as<decltype(std::declval<TestObject>().Get()), std::shared_ptr<TestObject>>);
static_assert(std::same_as<decltype(std::declval<const TestObject>().Get()), std::shared_ptr<const TestObject>>);
static_assert(std::same_as<decltype(std::move(std::declval<TestObject>()).Get()), std::shared_ptr<TestObject>>);The main thing we are testing for here is that SharedFromThis always returns a value of type std::shared_ptr<T> and that the cv qualifiers of T are maintained. We are doing this with more use of “deducing this”, and decltype(auto). If there was some invocation of SharedFromThis that returned a reference, decltype(auto) would ensure that that was detected.
My attempt at adding to the example in cppreference.com
At this point, I have talked about everything I really wanted to talk about and walk through, but before I write the conclusion, I think it would be fun to try and write a succinct example for this “Bestest” class that could be added to the cppreference page of std::enable_shared_from_this.
So here it goes:
// Pass key is out side the class so that we can write a concept for Objects
class ObjectToken
{
friend class Object;
ObjectToken() = default;
};
class Object;
// Concept to note the expectations of any class that derives from Object
// 1. Must be derived from Object to be considered an Object
// 2. Must be constructible from an ObjectToken and the provided parameters
// 3. Not an Object
template<typename T, typename... Params>
concept ObjectType =
std::derived_from<T, Object> &&
std::constructible_from<T, ObjectToken, Params...> &&
!std::same_as<T, Object>;
// Inherit privately. enable_shared_from_this is an implementation detail that does
// not need to be public
class Object
: std::enable_shared_from_this<Object>
{
public:
Object() = delete;
explicit Object(ObjectToken) {};
// Explicitly delete the copy and move constructors
// Derivations can create a Clone interface if they want to support copying
Object(const Object&) = delete;
Object(Object&&) = delete;
Object& operator=(const Object&) = delete;
Object& operator=(Object&&) = delete;
virtual ~Object() = default;
// Everyone has to use the factory function.
// Hence all objects from derivations of Object will be contained in a std::shared_ptr
template<typename T, typename... ParamTypes>
requires ObjectType<T, ParamTypes...>
static std::shared_ptr<T> New(ParamTypes&&... InArgs)
{
return std::make_shared<T>(ObjectToken{}, std::forward<ParamTypes>(InArgs)...);
}
protected:
// protected member function to allow derived classes to cast the result of shared_from_this to the derived type.
template<typename ThisType>
auto SharedFromThis(this ThisType&& InThis)
{
return std::static_pointer_cast<std::remove_reference_t<ThisType>>(std::forward<ThisType>(InThis).shared_from_this());
}
};
// ...
void testObject()
{
// Create a simple derivation that can return a pointer to itself
struct TestObject : Object
{
TestObject(ObjectToken InToken) : Object(InToken) {}
auto Get()
{
return SharedFromThis();
}
};
std::shared_ptr<TestObject> myObject = Object::New<TestObject>();
// Get() can return a shared_ptr of the same type as itself
std::shared_ptr<TestObject> myReference = myObject->Get();
// Still won't compile just like with Best
//TestObject stackObject{ObjectToken{}};
}Summary
The main takeaways I want people to come away from this article are:
std::enable_shared_from_thisis a really complex facility provided by the standard library.- The example given in
std::enable_shared_from_thisis fantastic - We can use Deducing this to wrap all of the complexities from
std::enable_shared_from_thisinto an easy to use correctly, and hard/impossible to use incorrectly base class to write simpler code. - We can use the Passkey Idiom to make parts of our APIs only accessible to certain other classes/functions.
For anyone that wants to play with the final code, here is a handy godbolt link to get you started!
Any thoughts or opinions are welcome in the comments below, and if you want to suggest a potentially better example from cppreference.com, please do let me know!