enable_shared_from_this

C++ Short

When I first ran into enable_shared_from_this it looked like something out of the bag of incomprehensible Boost tricks. However, if you consider the problem it solves and try solving it ab initio, it is, curiously, not very complicated. Here's the problem: write a class instance method that returns a reference-counted pointer to the instance itself. In other words, write a method that can wrap this inside a shared_ptr and return it. Here's a simplified use-case for such a thing.

Consider the task of writing an two classes: student and course, as shown below:

// forward declaration
struct student;

// models a course being offered
struct course
{
  // enroll a student for this course
  void enroll(const shared_ptr<student>& s){ enrolled_.push_back(s); }
private:
  // the list of enrolled students.
  vector<shared_ptr<student >> enrolled_;
};

// models a student
struct student
{
  // offer a course and the student enrolls if interested
  void offer(course& c) { if(interested_in(c)) { c.enroll(shared_from_this()); } }
  // how do we implement this?
  virtual shared_ptr<student> shared_from_this() = 0;
private:
  // internal implementation of what interests a student
  bool interested_in(course& c) { return true; }
};

So how does one implement the student::shared_from_this? We can't just wrap and return this, can we?

struct obviously_bad_student: public student
{
  shared_ptr<student> shared_from_this(){ return shared_ptr<student>(this); }
};

int main()
{
  course math;
  shared_ptr<student> s = make_shared<obviously_bad_student>();
  s->offer(math); // invalid delete
} 

Of course not, this will eventually end up with an invalid delete. Conceptually, it isn't different from the following:

void test_sp_bad_usage()
{
  int *i = new int; // the raw pointer
  shared_ptr<int> p(i); // wrap it up
  shared_ptr<int> q(i); // wrap it again (bad)
} // <- invalid delete (double delete)

How about storing the shared_ptr instance inside the student object itself and returning it back. It messes up the student abstraction, but, how about the following?

struct self_aware_leaky_student: public student
{
  // let an instance know its wrapped in a shared pointer
  void know_thyself(shared_ptr<student> s) { self_ = s; }
  // return the stored pointer
  shared_ptr<student> shared_from_this() { return self_; }
private:
  shared_ptr<student> self_;
};

int main()
{
  course math;
  shared_ptr<self_aware_leaky_student> s = make_shared<self_aware_leaky_student>();
  s->know_thyself(s);
  s->offer(math);
  assert(s.use_count()==3); // enrolled_, s, s->self_ (should be 2)
}

As the final assert confirms, this creates a cyclic reference and the instance will never be freed up. What we need to get this to work is a weak_ptr. Here's a simple test that illustrates its usage:

void test_wp_expected_usage()
{
  shared_ptr<int> p(new int); // a shared pointer
  weak_ptr<int> w(p); // make a weak reference
  {
    assert(1 == p.use_count()); // only 1 owner: p
    shared_ptr<int> q = w.lock(); // encash the weak ref
    assert(2 == q.use_count()); // 2 owners now: p, q
  } // q goes out of scope
  assert(1 == w.use_count()); // we're back to one owner
  p.reset(); // explicitly reset p.
  assert(w.expired()); // w knows it is dangling now.
}

A weak_ptr does not claim ownership to the underlying object, it is just a rain-check we keep to claim ownership in the future, if we need to. We can't access the underlying pointer directly from a weak_ptr, we need to lock it first, and in doing so create a shared_ptr that shares ownership with the original share_ptr.

With this new tool in hand, here's another attempt:

struct almost_right_student: public student
{
  // let an instance know its wrapped in a shared pointer
  void know_thyself(shared_ptr<student> s) { self_ = s; }
  shared_ptr<student> shared_from_this() { return self_.lock(); }
private:
  weak_ptr<student> self_;
};

int main()
{
  course math;
  shared_ptr<almost_right_student> s = make_shared<almost_right_student>();
  s->know_thyself(s);
  s->offer(math);
  assert(s.use_count()==2); // enrolled_, s
}

This works, there are no leaks and no double deletes. The only problem is that we've polluted the student interface with implementation details. Users of the student class must ensure that know_thyself was invoked before using the instance. That's not nice, but is there an alternative? Yes, and it's called (surprise!) enable_shared_from_this.

A class T can inherit from enable_shared_from_this<T> to inherit the shared_from_this member functions that obtain a shared_ptr instance pointing to *this. 20.7.2.4.1 in C++ Standard

The standard then goes on to suggest a possible implementation which is quite like the one we just wrote (but a lot better).

// 20.7.2.4.10
template<class T> class enable_shared_from_this {
private:
  weak_ptr<T> __weak_this;
protected:
  constexpr enable_shared_from_this(): __weak_this() {}
  enable_shared_from_this(enable_shared_from_this const &) {}
  enable_shared_from_this& operator=(enable_shared_from_this const &) { return *this; }
  ~enable_shared_from_this() {}
public:
  shared_ptr<T> shared_from_this() { return shared_ptr<T>(__weak_this); }
  shared_ptr<T const> shared_from_this() const { return shared_ptr<T const>(__weak_this); }
};

Finally, it puts the last bit that was missing from our solution.

The shared_ptr constructors that create unique pointers can detect the presence of an enable_shared_from_this base and assign the newly created shared_ptr to its __weak_this member. 20.7.2.4.11 in C++ Standard

In other words, if the class extends enable_shared_from_this the standard implementation of shared_ptr will invoke the equivalent of know_thyself to populate the weak reference without the end users having to do it explicitly. So, in conclusion, the best implementation of the student class, as we require it, is quite simply:

struct student : public enable_shared_from_this<student>
{
  // offer a course and the student enrolls if interested
  void offer(course& c) { if(interested_in(c)) { c.enroll(shared_from_this()); } }
private:
  // internal implementation of what interests a student
  bool interested_in(course& c) { return true; }
}

That's about it. The code in this post, should you have a use for it is here.