why must a Boost.Asio handler be copy constructible?

夙愿已清 提交于 2021-02-18 20:51:52

问题


According to http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/Handler.html, a handler provided to io_service::post must be copy constructible.

However, this excludes a scenario where a socket is accepted, and the response handler is moved, guaranteeing me that there is only one handler for the job:

auto socket = std::make_unique<socket>();
accepter.accept(*socket);

service.post([s{std::move(socket)}] {
  asio::write(*s, buffer("response"), ignored_err);
});

So why is this copy constructible requirement?

-- EDIT --

Clarification: I would like to make my code assure that only one instance of a handler is present. This makes it a lot easier to reason about it.

For a lot of programs, CopyConstructible is too strict a requirement, MoveConstructible is more appropriate.

    auto i = std::make_unique<int>();
    auto handler_w_resource = [i{std::move(i)}]{ ++(*i);};
    // auto copied = handler_w_resource; --> +1: lambda can't be copied!
    auto moved = std::move(handler_w_resource);

Therefor I was quite surprised that it could not be moved in:

    service.post(std::move(moved)); // :( post accepts no rvalue

回答1:


I cannot identify any material that explains the reasoning as to why handlers are required to be CopyConstructible, but it is explicitly noted even in the presence of C++11 support. The Movable Handlers documentation notes:

As an optimisation, user-defined completion handlers may provide move constructors, and Boost.Asio's implementation will use a handler's move constructor in preference to its copy constructor. In certain circumstances, Boost.Asio may be able to eliminate all calls to a handler's copy constructor. However, handler types are still required to be copy constructible.

The type checking Asio performs allows for friendlier compiler error messages when a type requirement is not satisfied. This type checking occurs earlier in the call stack, and is not predicated on if the use of the object would generate a compiler error. For example, when type checking is enabled, type checking will emit a compiler error for a handler that does not have a copy constructor, even if all calls to the handler's copy constructor have been eliminated. By defining BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS, one can disable the explicit type checking, and allow for compiler errors to surface from call points deeper in the implementation if they occur. The History notes this option:

Asio 1.6.0 / Boost 1.47

  • ...
  • Added friendlier compiler errors for when a completion handler does not meet the necessary type requirements. When C++0x is available (currently supported for g++ 4.5 or later, and MSVC 10), static_assert is also used to generate an informative error message. This checking may be disabled by defining BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS.

Here is a complete example demonstrating this functionality. Within it, ownership of a socket managed by unique_ptr is transferred to a handler via std::move():

#include <functional> // std::bind
#include <memory>     // std::unique_ptr
#include <string>     // std::string
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>

const auto noop = std::bind([]{});

int main()
{
  using boost::asio::ip::tcp;  

  // Create all I/O objects.
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
  auto socket1 = std::make_unique<tcp::socket>(std::ref(io_service));
  tcp::socket socket2(io_service);

  // Connect the sockets.
  acceptor.async_accept(*socket1, noop);
  socket2.async_connect(acceptor.local_endpoint(), noop);
  io_service.run();
  io_service.reset();

  // Move ownership of socket1 to a handler that will write to the
  // socket.
  const std::string expected_message = "test message";
  io_service.post([socket1{std::move(socket1)}, &expected_message] {
    boost::asio::write(*socket1, boost::asio::buffer(expected_message));
  });
  io_service.run();

  // Read from socket2.
  std::vector<char> actual_message(socket2.available());
  boost::asio::read(socket2, boost::asio::buffer(actual_message));

  // Verify message.
  assert(std::equal(
    begin(expected_message), end(expected_message),
    begin(actual_message), end(actual_message)));
}



回答2:


It's just a convention.

The convention can be found throughout many C++ libraries, including all your standard library algorithms.

The convention makes it a lot easier to

  • implement the semantics correctly
  • know what to expect
  • let the compiler do deep optimizations (references kill many optimizations due the aliasing problem)

So, if you need a handler by reference, just pass std::ref(f). It was designed for the purpose.


Beware of lifetime issues - as always when using references.



来源:https://stackoverflow.com/questions/37709819/why-must-a-boost-asio-handler-be-copy-constructible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!