If you make a class T that derives from std::enable_, then the creation of a std::shared_ptr to that class will activate the shared_ method to return a shared_ptr that shares ownership with the originally-created std::shared_ptr.
The catch is that the shared_ptr constructor and enable_ are in cahoots, and the shared_ptr must be able to access the enable_ in order to finish the job. This requires that you publicly derive from std::enable_:
class MyClass : public std::enable_shared_from_this<MyClass>
{
...
};
If you forget the public keyword, then the base class defaults to private, and the secret signal between shared_ptr and enable_ does not get through.
Here’s how enable_ and shared_ptr work together. Note that I’ve ignored edge cases; the idea here is to give the basic idea so you can diagnose enable_ issues yourself.
template<typename T>
struct enable_shared_from_this
{
shared_ptr<T> shared_from_this()
{ return shared_ptr<T>(weak_this); }
weak_ptr<T> weak_this;
};
template<typename T>
struct shared_ptr
{
shared_ptr(T* p) : ptr(p)
{
if (T derives from enable_shared_from_this) {
ptr->weak_ptr = *this;
}
}
T* ptr;
/* other stuff */
};
When a shared_ptr is created, it snoops at the managed object to see if it derives from enable_. If so, then it sets the weak_ptr to hold a weak pointer to the shared object. When you later ask for a shared_, it promotes this weak pointer to a shared pointer and returns it.
Okay, so we already see some consequences and pitfalls:
First of all, if you fail to derive publicly from enable_, the feature simply fails silently. There is no diagnostic that says, “Hey, like, you’re deriving from enable_, but you did it privately, so it’s not going to work.”¹
Second, notice that the weak pointer is set only when the object is placed inside a shared_ptr, which happens after the shared object has been constructed. This means that you cannot use shared_ in your constructor.
Third, if the object is not wrapped inside a shared_ at all, then shared_ will always fail. For example, if somebody constructs the object on the stack, or via new or make_unique, it will not be controlled by a shared_.
There are so many ways enable_ can go wrong. Next time, we’ll see what we can do to guard against them.
¹ Maybe it’s possible to add a diagnostic to shared_. I wonder if the shared type is required to be complete by that point.
0 comments