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 fromenable_shared_from_this<T>
to inherit theshared_from_this
member functions that obtain ashared_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 anenable_shared_from_this
base and assign the newly createdshared_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.