Need for enable_shared_from_this

I'm about to call a function that requires as parameter a shareable pointer to an object of a given class, but the fact is that I do not have such a beast at hand, just a raw pointer. That is quite a nuisance, because normally there is no way to go from a raw pointer to a shared pointer that already owns it.

But if our C++ compiler is 0x compliant, or if we can use the Boost libraries, we have access to a reasonable simple mechanism to overcome this issue: just derive the class we are interested in from enable_shared_from_this and then use shared_from_this() to create the required shared_ptr.

Actually, things are not so simple. There are a few details we should be aware of. But, first things first, let's see what happens if we don't rely on enable_shared_from_this.

Given a class, that I have imaginatively called Something, the preferred way to create a shared pointer to an object is:
std::shared_ptr<Something> sp1(new Something);
In this way we stress the fact that we don't expect any part of the subsequent code to directly use the raw pointer. But this is just an hint. We can perform the same operation in two steps
Something* ps = new Something;
std::shared_ptr<Something> sp(ps);
Besides, given a shared pointer we can always extract its raw pointer - even though that is not often a good idea - using the shared_ptr::get() method.

In any case, typically the issue arises because we are in a Something (I mean, our class) method and we want to use a shared pointer to "this" object. A (bad) idea would be to create on the fly a shared pointer from "this":
std::shared_ptr<Something> Something::badIdea() { return std::shared_ptr<Something>(this); }
And using the resulting smart pointer:
void Something::aMethod()
{
std::shared_ptr<Something> spBad = this->badIdea();
if(spBad.use_count() == 1)
std::cout << "Trouble!" << std::endl;
}

A conspicuously bad idea, and would lead to a nightmarish behaviour of our code.

We would expect to use that code in a context like:
std::shared_ptr<Something> sp1(new Something);
sp1->aMethod();

But this is plain asking for trouble. We ends up having two unrelated shared_ptr, each of them rightfully thinking to have the resource ownership. Both of them, when exiting their respective scope, would call delete on the raw pointer, and that means undefined (usually catastrophical) behavior.

So, we can't use aMethod() from a shared_ptr, that make it completely useless, since we designed it thinking exactly for that scenario. But look at this code:
Something* ps = new Something;
ps->aMethod();

It looks kind of weird. There is no call to delete ps, and aMethod() actually behaves like a dtor, making ps invalid after its call. So, if we want to avoid disasters we should heavily comment any usage of aMethod() or - better - avoid writing code like that.

Even worse, the user could call aMethod from an object on the stack:
Something s;
s.aMethod();

Calling delete for an object allocated on the stack is pure nonsense, but in aMethod() we create a shared_ptr from "this", implicitly assuming it refers to a memory location on the heap, with all the (terrible) consequences of the case.

So, definitely it is not a good idea using that approach. We should instead enhance our class, providing a way of keeping track of an eventual related shared_ptr, so that we can refer to it when we create another shared_ptr instance for the same object on the heap. That is the task for which enable_shared_from_this has been created, and in the next post we see how to use it.

No comments:

Post a Comment