c++boostasiobeast

Why we can reuse a moved socket_ in acceptor_.async_accept?


Reference: https://www.boost.org/doc/libs/1_35_0/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept/overload1.html

boost::asio::ip::tcp::acceptor acceptor(io_service);
...
boost::asio::ip::tcp::socket socket(io_service); 
// you have to initialize socket with io_service first before 
//you can use it as a parameter on async_accept.
acceptor.async_accept(socket, accept_handler);

Reference:

https://github.com/vinniefalco/CppCon2018/blob/master/listener.cpp

listener::  listener(
    net::io_context & ioc,
    tcp::endpoint endpoint,
    std::shared_ptr < shared_state >
    const & state): acceptor_(ioc), socket_(ioc), state_(state) {

// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
    tcp::acceptor acceptor_;
    tcp::socket socket_;
...
}

// Handle a connection
void listener::  on_accept(error_code ec) {
    if (ec)
      return fail(ec, "accept");
    else
      // Launch a new session for this connection
      std::make_shared < http_session > (
        std::move(socket_),                    // socket_ is moved here?
        state_) -> run();

    // Accept another connection
    acceptor_.async_accept(
      socket_,                                 // why we still can use it here?
      [self = shared_from_this()](error_code ec) {
        self -> on_accept(ec);
      });
  }

Based on my understanding, std::move(socket_) allows the compiler to cannibalize socket_. In other word, the listener::socket_ originally initialized by socket_(ioc) will become uninitialized.

Question> How can we give an uninitialized socket_ to acceptor_.async_accept?

Thank you


Solution

  • It all depends on the implementation of the types.

    We can loosely describe the intent of a move as "the compiler is allowed to cannibalize". But really, for user-defined types we're going to have to tell it how to do that, exactly.

    In language "doctrine" a moved-from object may only be assumed safe to destruct, but in practice many libraries make more lenient guarantees (e.g. keeping all the invariants, or making sure that a moved-from object is comparable to a newly constructed one).

    Indeed, ASIO documents this:

    Remarks

    Following the move, the moved-from object is in the same state as if constructed using the basic_stream_socket(const executor_type&) constructor.