Been playing a lot with Boost.Asio of late. I like the library a lot since it offers a fantastic way to squeeze performance out of today's multicore systems.
A question I have asked myself a few times, and I thought worth throwing out there regards object lifespan / ownership when making async calls with Asio.
The problem I've come accross repeatedly is that you quite often have to "expire" an object that still has async callbacks pending against it. If that object goes out of scope before the callback is invoked things inevitably go bang.
To combat this I've taken to using the boost::enable_shared_from_this
template as a base class for most asio based classes. This works OK but it's a little burdensome: usually this also means protecting the constructor and adding a factory method to the class to ensure that all instances are created inside a shared_ptr.
I just wanted to know how other people had tackled this problem. Am I going about this the best way? Or have I got my Asio.Foo all wrong?
Discuss... :)
Using
boost::enable_shared_from_this
is pretty much the way to do it. Additionally, look at usingboost::weak_ptr
if you need references to the object that should not preserve the object if they are the only references which remain.A good example of using
weak_ptr
: I useenable_shared_from_this
in my socket class which utilizesboost::asio
. Theboost::asio
framework is the only thing that stores persistent references to the object, via read and write handlers. Thus, when the socket's destructor is called, I know that the socket is closed and I can "do stuff" in a handler to clean up for that closed socket. The application which uses the socket only has aweak_ptr
reference to it, which it promotes to ashared_ptr
when it wants to work with the socket (usually to write to it). That promotion can be checked for failure in case the socket went away, although the socket's close handler usually cleans up all theweak_ptr
references appropriately before that even happens.That kind of thing isn't limited to Asio. I recently wrote a thread-pool class (using Boost::Thread) that had pretty much the same problem -- the threads would call the thread-pool class that created them to see what task they had to do next, using a plain pointer to it, and if the thread-pool class were destroyed with a child thread still running, the program would crash. I dealt with it by calling
interrupt
on each of the threads in the thread-pool destructor, then waiting for all of them to exit before letting the destructor return.If I understand your shared-pointer solution, it seems to be doing the same general thing -- ensuring that the item can't be destroyed until it's no longer needed. An aesthetically pleasing solution too. I don't see any better answer to this kind of problem.