Using enable_shared_from_this

In the previous post we have seen where the need for enable_shared_from_this arise. Now we have a look at how actually use it.

To put it simply, our class should derive from the template class enable_shared_from_this and should use the method shared_from_this() to create a shared_ptr, when that make sense.

Here too I explicitly refer to the standard implementation (std), if your compiler does not support it yet, you can use Boost instead, just change the namespace to boost and refer to the proper files.

We declare a class in this way:
class SomethingElse : public std::enable_shared_from_this<SomethingElse>
{
// ...
};

So that we can use the public method shared_from_this() to create a shared_ptr to SomethingElse. In case it makes no sense calling shared_from_this(), but we still call it, the method throws an exception, to let us know we are in trouble. This could look like a nuisance, but actually it's a huge step ahead with respect to having an undefined behaviour.

Have a look at the definition of a simple public method for our class:
void aMethod()
{
try
{
std::shared_ptr<SomethingElse> sp = shared_from_this(); // 1.
if(sp.use_count() > 1)
std::cout << "As expected" << std::endl;
}
catch(std::exception& ex) // 2.
{
std::cout << "Exception: " << ex.what() << std::endl;
std::cout << "You should use this method only from a shared_ptr!" << std::endl;
}
}

1. Here we create a shared_ptr using shared_from_this(), we don't have much to do with it here, actually, so we just check its internal count, that we expect to be more than one, since we assume that "this" was already owned by another shared_ptr. But what if our assuption is wrong?
2. If we try to call shared_from_this(), and no shared_ptr is already owning "this", we'll end up here. Since aMethod() doesn't have much to do, we simply log to the output console a message.

Now we can correctly call aMethod() in this way:
std::shared_ptr<SomethingElse> spse(new SomethingElse);
spse->aMethod();

It is still a mistake calling aMethod() from an object on the stack, or in the heap but not associated to a shared_ptr. But at least we could design our code to avoid crashes even though the class user call it anyway:
SomethingElse se;
se.aMethod(); // error message printed to cout

SomethingElse* pse = new SomethingElse;
pse->aMethod();
delete pse; // error message printed to cout

Last but not least, we should remember that makes no sense calling shared_from_this() where "this" is not available. Most noticeable, that is the case of the constructor. Look at this code:
class SomethingWrong : public std::enable_shared_from_this<SomethingWrong>
{
public:
SomethingWrong()
{
try
{
std::shared_ptr<SomethingWrong> sp = shared_from_this();
if(sp.use_count() > 1)
std::cout << "Not expected anymore" << std::endl;
}
catch(std::exception& ex)
{
std::cout << "Expected exception: " << ex.what() << std::endl;
}
}
};

The SomethingWrong usage of shared_from_this() is the same of SomethingElse's. But here we are trying to use "this" before "this" would be actually available. So, even if we'll try to use our class in a way that we would have expected to be corrected:
std::shared_ptr<SomethingWrong> spse(new SomethingWrong);
we always and up with having a trapped exception in the object ctor.

No comments:

Post a Comment