Boost logo

Boost :

Subject: Re: [boost] Should pass boost::asio::io_service by raw pointer or smart pointer?
From: Gavin Lambert (boost_at_[hidden])
Date: 2019-01-07 01:15:56


On 26/12/2018 16:12, Vinnie Falco wrote:
> The caller is responsible for ensuring this object is not destroyed
> while there is an outstanding operation. Similar to how a socket is
> treated, a `delayed_runner` would be a data member of some "session"
> object which is itself managed by `shared_ptr`. Completion handlers
> use a "handler owns I/O object" shared ownership model, so the
> function object contains a bound copy of the shared pointer, ensuring
> that the lifetime of the I/O object extends until the function object
> is invoked or destroyed.

I think people are mixing up the object layers, or following some older
(erroneous) advice to "if in doubt, use shared_ptr everywhere".

As I understand it, the general recommendation when using ASIO is to
implement a higher-level object (eg. "session") which contains the
underlying ASIO object (eg. "socket") by value, along with other related
state such as strands, streambufs, diagnostic info, etc -- all stored by
value.

The higher-level object ("session") itself, however, should be given
shared ownership (and kept in a shared_ptr) -- and thus generally
non-copyable and non-moveable. And the shared_ptr (itself, not just
"this") must be copied into handlers either via a lambda-capture or
explicit parameter. This usually requires using shared_from_this(),
although some other designs are possible.

This is required in order to ensure that the session object exists as
long as outstanding handlers exist, to avoid invoking callbacks with a
deleted "this" and causing UB.

Keeping the session object alive will inherently keep the socket,
buffers, and other member state alive, so they don't need to themselves
be shared_ptrs.

Having the session object not be in a shared_ptr is theoretically
possible but not really practical, as you'd have to be able to guarantee
that its destructor is either not called until or does not return until
all outstanding handlers involving that object have been called or
discarded, and this isn't really feasible. (Closing or destroying a
socket will cancel any pending operations but does not guarantee that
the operations' completion handlers are called synchronously. There is
a way to block until the handlers have been called, but this can
deadlock if called in the wrong context.)


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk