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.)