I frequently see this pattern in code, binding shared_from_this
as the first parameter to a member function and dispatching the result using an async_*
There is no conversion from boost::shared_ptr<Connection>
(the return type of shared_from_this
) to Connection*
(the type of this
), as it would be unsafe as you rightfully pointed out.
The magic is in Boost.Bind. To put it simply, in a call of the form bind(f, a, b, c)
(no placeholder or nested bind expression involved for this example) where f
is a pointer to member, then calling the result of the call will result in a call of the form (a.*f)(b, c)
if a
has a type derived from the class type of the pointer to member (or type boost::reference_wrapper<U>
), or else it's of the form ((*a).*f)(b, c)
. This works with pointers and smart pointers alike. (I'm actually working from memory the rules for std::bind
, Boost.Bind is not exactly identical but both are in the same spirit.)
Furthermore, the result of shared_from_this()
is stored in the result of the call to bind
, ensuring that there are no lifetime issues.
Maybe I'm missing something obvious here, but the shared_ptr returned by shared_from_this()
is stored in the function object returned by boost::bind
, which keeps it alive. It's only implicitly converted to Connection*
at the time when the callback is launched when the async read finishes, and the object is kept alive for at least the duration of the call. If the handle_Receive
doesn't create another shared_ptr from this, and the shared_ptr that was stored in the bind functor is the last shared_ptr alive, the object will be destroyed after the callback returns.
In short, boost::bind
creates a copy of the boost::shared_ptr<Connection>
that is returned from shared_from_this()
, and boost::asio
may create a copy of the handler. The copy of the handler will remain alive until one of the following occurs:
run()
, run_one()
, poll()
or poll_one()
member function has been invoked.io_service
is destroyed.io_service::service
that owns the handler is shutdown via shutdown_service().Here are the relevant excerpts from the documentation:
boost::bind documentation:
The arguments that
bind
takes are copied and held internally by the returned function object.
boost::asio io_service::post:
The
io_service
guarantees that the handler will only be called in a thread in which therun()
,run_one()
,poll()
orpoll_one()
member functions is currently being invoked. [...] Theio_service
will make a copy of the handler object as required.
boost::asio io_service::~io_service:
Uninvoked handler objects that were scheduled for deferred invocation on the
io_service
, or any associated strand, are destroyed.
Where an object's lifetime is tied to the lifetime of a connection (or some other sequence of asynchronous operations), ashared_ptr
to the object would be bound into the handlers for all asynchronous operations associated with it. [...] When a single connection ends, all associated asynchronous operations complete. The corresponding handler objects are destroyed, and allshared_ptr
references to the objects are destroyed.
While dated (2007), the Networking Library Proposal for TR2 (Revision 1) was derived from Boost.Asio. Section 5.3.2.7. Requirements on asynchronous operations
provides some details for the arguments to async_
functions:
In this clause, an asynchronous operation is initiated by a function that is named with the prefix
async_
. These functions shall be known as initiating functions. [...] The library implementation may make copies of the handler argument, and the original handler argument and all copies are interchangeable.The lifetime of arguments to initiating functions shall be treated as follows:
- If the parameter is declared as a const reference or by-value [...] the implementation may make copies of the argument, and all copies shall be destroyed no later than immediately after invocation of the handler.
[...] Any calls made by the library implementation to functions associated with the initiating function's arguments will be performed such that calls occur in a sequence call1 to calln, where for all i, 1 ≤ i < n, calli precedes call i+1.
Thus:
shared_ptr<Connection>
, increasing the reference count of the Connection
instance while the copies of handler remain alive.io_serive::service
is shutdown or the io_service
is destroyed. In the example, the copies of handler will be destroyed, decreasing the reference count of Connection
, and potentially causing the Connection
instance to be destroyed.Connection
, and potentially causing it to be destroyed.asnyc_
's arguments, will be executed sequentially, and not concurrent. This includes io_handler_deallocate
and io_handler_invoke
. This guarantees that the handler will not be deallocated while the handler is being invoked. In most areas of the boost::asio
implementation, the handler is copied or moved to stack variables, allowing the destruction to occur once execution exits the block in which it was declared. In the example, this ensures that the reference count for Connection
will be at least one during the invocation of the handler.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.
It goes like this:
1) Boost.Bind documentation states:
"[Note: mem_fn creates function objects that are able to accept a pointer, a reference, or a smart pointer to an object as its first argument; for additional information, see the mem_fn documentation.]"
2) mem_fn documentation says:
When the function object is invoked with a first argument x that is neither a pointer nor a reference to the appropriate class (X in the example above), it uses get_pointer(x) to obtain a pointer from x. Library authors can "register" their smart pointer classes by supplying an appropriate get_pointer overload, allowing mem_fn to recognize and support them.
So, pointer or smart-pointer is stored in the binder as-is, until its invocation.