enable_shared_from_this - overview, examples, and internals

Public inheritance from enable_shared_from_this<T> enables a class T to create additional shared_ptr instances to itself. Typically, enable_shared_from_this holds a weak_ptr to create shared_ptr on demand.

Tutorial | May 24, 2020 | hkumar 

Some Basics

std::shared_ptr is a shared ownership smart pointer that allows us to access and manage an object's lifetime safely. Multiple instances of shared_ptr control the lifetime of an object through a shared control block structure. A control block maintains a reference count, a weak count, and other necessary information to manage the object's existence in memory.

A new control block is created when a shared_ptr is constructed or initialized with a raw pointer. To ensure that an object is managed by only one shared control block, any additional instances of shared_ptr to an object must be produced by copying an already existing shared_ptr to that object, e.g.:

void good() {
 auto p{new int(10)}; //p is int*
 std::shared_ptr<int> sp1{p}; 
 //Create additional shared_ptr from an existing shared_ptr
 auto sp2{sp1}; //OK. sp2 shares control block with sp1
}

We can obtain further shared_ptr instances through an existing weak_ptr too, but we will talk more on that later.

Initializing a shared_ptr with a raw pointer to an object that is already managed by a shared_ptr creates another control block to manage the object, which results in undefined behavior. For instance:

void bad() {
 auto p{new int(10)};   
 std::shared_ptr<int> sp1{p};
 std::shared_ptr<int> sp2{p}; //! Undefined Behavior
}

Instantiating more than one shared_ptr from one raw pointer is a programmatic oversight with severe consequences. Use std::make_shared (or std::allocate_shared) wherever possible to lessen the likelihood of that error. After all, you are unlikely to encounter this kind of code unless someone intends to shoot themself on foot:

auto sp1 = std::make_shared<int>();
std::shared_ptr<int> sp2{sp1.get()}; //! Bad, but who would do that?

However, there are situations where a shared_ptr managed object needs to acquire a shared_ptr to itself. We would see a more compelling example of that situation in the next section. But first, an attempt like the following to create a shared_ptr from this pointer would not work for the above-described reason:

struct Egg {
 std::shared_ptr<Egg> getSelfPtr() {
  return std::shared_ptr<Egg>(this); //!! Bad
 }
 //...
};

void spam() {
 auto sp1 = std::make_shared<Egg>();
 auto sp2 = sp1->getSelfPtr(); //!! Undefined Behavior
 /*sp1 and sp2 have two different control blocks 
     managing same Egg*/
}

That's where the std::enable_shared_from_this<T> comes in. A class that publicly inherits std::enable_shared_from_this can acquire a shared_ptr to itself by calling the method shared_from_this(). Here is a basic example of it:

//some API's header
struct Thing; 
void some_api(const std::shared_ptr<Thing>& tp); 

//------- Different file ----
//Note: public inheritance
struct Thing : std::enable_shared_from_this<Thing> {
 void method() {
  some_api(shared_from_this()); //!! Good. 
  //...
 }
};    

void foo() {
 auto sp = std::make_shared<Thing>();
 sp->method();
 //..
}

Why shared_ptr from 'this'?

Let's consider a more convincing example situation where a shared_ptr managed object needs to acquire a shared_ptr to itself.

A Processor class processes data asynchronously and saves it to a database. On receiving data, Processor processes the data asynchronously through a custom Executor:

class Executor {
public:
 //Executes a task asynchronously
 void
 execute(const std::function<void(void)>& task);
 //....
private:
 /* Contains threads and a task queue */
};

class Processor {
public:
 //...
 //Processes data asynchronously. (implemented later)
 void processData(const std::string& data); 

private:
 //Called in an Executor thread 
 void doProcessAndSave(const std::string& data) {
    //1. Process data
    //2. Save data to DB
 }
 //.. more fields including a DB handle..
 Executor* executor;
};

Below, a Processor receives some data from a Client class that holds a shared_ptr to it:

class Client {
public:
 void someMethod() {
  //...
  processor->processData("Some Data");
  //..do something else
 }
private:
 std::shared_ptr<Processor> processor;
};

An Executor is a threadpool that encapsulates a number of threads along with a task queue and executes disparate callables from the queue.

In Processor::processData, we need to pass a callable or a lambda to the Executor for asynchronous execution. The lambda calls the private method Processor::doProcessAndSave that does the actual processing of data before saving it to a database. Therefore, the lambda needs to capture a reference to the Processor object itself; we could do so by capturing this pointer:

void Processor::processData(const std::string& data) {
 executor->execute([this, data]() { //<--Bad Idea
   //Executes in an Executor thread asynchronously
   //'this' could be invalid here.
   doProcessAndSave(data);
 });
}

However, a Client is free to trash or reset the shared_ptr to its associated Processor anytime for various reasons, which might destroy the Processor. Therefore, the captured this pointer could be invalidated anytime before or during the execution of the enqueued lambda.

We can avoid the above described undefined behavior by capturing a shared_ptr to the current ('this') Processor object in the lambda. A Processor will stay alive as long as an enqueued lambda holds a shared_ptr to it.

The following illustration shows what we want:


Processor Client Executor Strong Ref


However, we know that naively creating a shared_ptr<Processor>(this) would not work.

We need a mechanism for a shared_ptr managed object to, somehow, get a hold on its control block to acquire another shared_ptr of itself. The std::enable_shared_from_this serves that purpose. We would talk about the concept behind std::enable_shared_from_this in the next sections. But first, let's look at our modified Processor class that makes use of it:

class Processor : public std::enable_shared_from_this<Processor> {
 //..same as above...
}; 

void Processor::processData(const std::string& data) {
 //shared_from_this() returns a shared_ptr<Processor> 
 //  to this Processor
 executor->execute([self = shared_from_this(), data]() { 
   //Executes in an Executor thread asynchronously
   //'self' is captured shared_ptr<Processor>
   self->doProcessAndSave(data); //OK. 
 });
}

Why std::enable_shared_from_this?

In essence, additional shared_ptr instances can only be produced from a handle that has access to the control block; that handle could be either a shared_ptr or a weak_ptr. If an object has that handle, it can produce additional shared_ptr to itself. However, a shared_ptr is a strong reference and influences the lifetime of the managed object. An object that keeps a shared_ptr to itself becomes immortal, thus causing a memory leak:

struct Immortal {
 //....
 std::shared_ptr<Immortal> self; //Refers to the containing object 
};

What we need here can be accomplished through weak_ptr. A weak_ptr is a weak reference that does not affect the managed object's lifetime, yet can be used to obtain a strong reference when required. If an object keeps a weak_ptr to itself, a shared_ptr can be acquired from that when needed. Here is a naive implementation of our idea:

class Naive {
public:

 //static factory method
 static
 std::shared_ptr<Naive> create() {
  //Note: std::make_shared won't work with private constructor here.
  auto sp = std::shared_ptr<Naive>(new Naive);
  //Save a weak_ptr as control block handle.
  sp->weakSelf = sp;
  return sp;
 }

 //returns std::future<void>
 auto asyncMethod() {
  //Acquire shared_ptr to self from the weak_ptr
  //Capture the shared_ptr.
  return
  std::async(std::launch::async, [self = weakSelf.lock()]() {
    //Async task executing in a different thread
    self->doSomething();
  });
 }

 void doSomething() {
  //...
  //Sleep to simulate some work.
  std::this_thread::sleep_for(std::chrono::seconds(2));
 }

private:
 Naive(){}
 Naive(const Naive&) = delete;
 const Naive& operator=(const Naive&) = delete;
 std::weak_ptr<Naive> weakSelf; //Weak reference to itself
};

void naiveTest() {
 std::future<void> fut;
 { // block
  auto pn = Naive::create();
  fut = pn->asyncMethod();
 }
 //The Naive object lives until the async task is finished
 fut.get(); //waits until the async work is done 
}

The above implementation is naive because it has many limitations. We need to make sure that the weak_ptr to self is initialized when a Naive is constructed; therefore, the construction of Naive must be constrained to only through a static factory method.

This solution imposes too many restrictions on design for a reasonable requirement. Nevertheless, this implementation sets the conceptual framework for the standard solution - std::enable_shared_from_this.

Internals of std::enable_shared_from_this

A typical implementation of std::enable_shared_from_this<T> is a class that only contains a weak_ptr<T> field (commonly called weak_this):

template<class T>
class enable_shared_from_this {
 mutable weak_ptr<T> weak_this;
public:
 shared_ptr<T> shared_from_this() {
  return shared_ptr<T>(weak_this); 
 }
 //const overload
 shared_ptr<const T> shared_from_this() const {
  return shared_ptr<const T>(weak_this); 
 }

 //..more methods and constructors..
 //there is weak_from_this() also since C++17

 template <class U> friend class shared_ptr;
};

The rest of the magic lies in the shared_ptr constructor. When a shared_ptr is initialized with a T* and if T inherits from std::enable_shared_from_this<T>, the shared_ptr constructor initializes weak_this. The code to initialize weak_this could be enabled at compile-time using trait classes (std::enable_if and std::is_convertible) only if T publicly inherits from std::enable_shared_from_this<T>. Note that the public inheritance is a requisite, for the shared_ptr constructor needs to access the weak_this for initialization.

A few more trivial details regarding the std::enable_shared_from_this are noteworthy here. One, weak_this is declared mutable so that it can be modified for const objects too. Two, the shared_ptr is declared as a friend so that it can access the private fields (weak_this).

Here is another basic example to illustrate how it is all tied up. The code that initializes a shared_ptr<Article> below is intentionally split into two steps to demonstrate the two stages of creation and initialization of the embedded weak_ptr<Article> (see the image following the code):

struct Article : std::enable_shared_from_this<Article> {
 //stuff..
};

void foo() {
 //Step 1
 //Enclosed 'weak_this' is not associated with any control block.
 auto pa = new Article;

 //Step 2
 //Enclosed 'weak_this' gets initialized with a control block
 auto spa = std::shared_ptr<Article>(pa);
}

The following image illustrates the above two steps of creation and initialization of weak_this in the Article object:


enable_shared_from_this 2 steps


std::bad_weak_ptr Exception

There is a restriction on calling shared_from_this, that it is permitted to be called only inside a shared_ptr managed object. Since C++17, calling shared_from_this in an object that is not managed by a shared_ptr throws std::bad_weak_ptr exception (it was Undefined Behavior until C++14). Consider the following:

struct Article : std::enable_shared_from_this<Article> {
 //...
 void something() {
  auto self = shared_from_this();
  //....
 }
};

void iThrow() {
 auto pa = new Article; 
 pa->something(); //! std::bad_weak_ptr
 //...
}

The exception is thrown by the shared_ptr's constructor when it is invoked by shared_from_this on an uninitialized or expired weak_ptr.

One more example of std::bad_weak_ptr is when shared_from_this is called in a constructor because the embedded weak_this is not yet initialized.

Another, very subtle and hard to pinpoint, case of std::bad_weak_ptr is when a class does not publicly inherit std::enable_shared_from_this and shared_from_this is called. Private (or protected) inheritance prevents the initialization of the weak_this and that could go unnoticed without any compiler warnings. For instance:

//Note: private inheritance
class Overlooked : std::enable_shared_from_this<Overlooked> {
public:
 void foo() {
   auto self = shared_from_this(); //std::bad_weak_ptr
    //...
   }
   //...
}; 

There are environments where the exceptions are disabled or avoided. For those situations, there is an alternative to shared_from_this since C++17.

The C++17 standard added weak_from_this() method to std::enable_shared_from_this, which returns a copy of the embedded weak_this. A shared_ptr can be safely acquired from that weak_ptr without any possibility of std::bad_weak_ptr, as follows:

struct Article : std::enable_shared_from_this<Article> {
 //...
 void something() {
  //weak_ptr<T>::lock does not throw
  if(auto self = weak_from_this().lock()) {
   //OK. use 'self'
  } else {
   std::cout << "Bad weak_this\n";
  }
 }
};

Conclusion

There are situations where a shared_ptr managed object needs to acquire an additional shared_ptr to itself. std::enable_shared_from_this is a standard solution that enables a shared_ptr managed object to acquire a shared_ptr to itself on demand.

A class T that publicly inherits an std::enable_shared_from_this<T> encapsulates a std::weak_ptr<T> to itself that can be converted to a std::shared_ptr<T> when needed.

There are some common pitfalls related to std::enable_shared_from_this<T> that can raise std::bad_weak_ptr exception while calling shared_from_this because the enclosed weak_ptr is not initialized. These are - calling shared_from_this when the object is not managed by a shared_ptr, calling shared_from_this in a constructor, calling shared_from_this when the class does not publicly inherit from std::enable_shared_from_this.

Finally, the name enable_shared_from_this could misleadingly suggest as if a class inheriting from it can create a shared_ptr from this pointer. But that's not what it does, because creating a shared_ptr from this is outright wrong. I prefer to think of it as enable_shared_from_self because that's what it does. It enables an object to acquire a shared_ptr from itself.

Read More on shared_ptr and weak_ptr

shared_ptr - basics and internals with examples - nextptr

Using weak_ptr for circular references - nextptr

2015 nextptr