I also see this pattern used a lot and (thanks to @Tanner) I can see why it's used when the io_service
is run in multiple threads. However, I think that there are still lifetime issues with it as it replaces a potential crash with a potential memory/resource leak...
Thanks to boost::bind, any callbacks that are bound to shared_ptrs become "users" of the object (increasing the objects use_count), so the object won't be deleted until all of the outstanding callbacks have been called.
The callbacks to the boost::asio::async* functions are called whenever, cancel or close
is called on the relevant timer or socket. Normally you would just make the appropriate cancel/close calls in the destructor using Stroustrup's beloved RAII pattern; job done.
However, the destructor won't be called when the owner deletes the object, because the callbacks still hold copies of the shared_ptrs and so their use_count will be greater than zero, resulting in a resource leak. The leak can be avoided by making the appropriate cancel/close calls prior to deleting the object. But it’s not as fool-proof as using RAII and making the cancel/close calls in the destructor. Ensuring that the resources are always freed, even in the presence of exceptions.
An RAII conforming pattern is to use static functions for callbacks and pass a weak_ptr to boost::bind when registering the callback function as in the example below:
class Connection : public boost::enable_shared_from_this<Connection>
{
boost::asio::ip::tcp::socket socket_;
boost::asio::strand strand_;
/// shared pointer to a buffer, so that the buffer may outlive the Connection
boost::shared_ptr<std::vector<char> > read_buffer_;
void read_handler(boost::system::error_code const& error,
size_t bytes_transferred)
{
// process the read event as usual
}
/// Static callback function.
/// It ensures that the object still exists and the event is valid
/// before calling the read handler.
static void read_callback(boost::weak_ptr<Connection> ptr,
boost::system::error_code const& error,
size_t bytes_transferred,
boost::shared_ptr<std::vector<char> > /* read_buffer */)
{
boost::shared_ptr<Connection> pointer(ptr.lock());
if (pointer && (boost::asio::error::operation_aborted != error))
pointer->read_handler(error, bytes_transferred);
}
/// Private constructor to ensure the class is created as a shared_ptr.
explicit Connection(boost::asio::io_service& io_service) :
socket_(io_service),
strand_(io_service),
read_buffer_(new std::vector<char>())
{}
public:
/// Factory method to create an instance of this class.
static boost::shared_ptr<Connection> create(boost::asio::io_service& io_service)
{ return boost::shared_ptr<Connection>(new Connection(io_service)); }
/// Destructor, closes the socket to cancel the read callback (by
/// calling it with error = boost::asio::error::operation_aborted) and
/// free the weak_ptr held by the call to bind in the Receive function.
~Connection()
{ socket_.close(); }
/// Convert the shared_ptr to a weak_ptr in the call to bind
void Receive()
{
boost::asio::async_read(socket_, boost::asio::buffer(read_buffer_),
strand_.wrap(boost::bind(&Connection::read_callback,
boost::weak_ptr<Connection>(shared_from_this()),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
read_buffer_)));
}
};
Note: the read_buffer_
is stored as a shared_ptr
in the Connection class and passed to the read_callback
function as a shared_ptr
.
This is to ensure that where multiple io_services
are run in separate tasks, the read_buffer_
is not deleted until after the other tasks have completed, i.e. when the read_callback
function has been called.