为什么Boost.Asio处理程序必须是可复制构造的?

时间:2016-06-08 18:07:21

标签: c++ boost boost-asio move-semantics

根据http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/Handler.html,提供给io_service::post的处理程序必须是可复制的。

但是,这排除了接受套接字并移动响应处理程序的情况,保证我只有一个处理程序:

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

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

那么为什么这个可复制的构造要求呢?

- 编辑 -

澄清:我想让我的代码确保只存在一个处理程序实例。这使得理由更容易。

对于很多程序来说, CopyConstructible 的要求太严格, MoveConstructible 更合适。

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

因此我很惊讶它无法移入:

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

2 个答案:

答案 0 :(得分:7)

我无法识别任何解释为什么处理程序必须为CopyConstructible的推理的材料,但即使存在C ++ 11支持,也会明确指出。 Movable Handlers文档说明:

  

作为优化,用户定义的完成处理程序可以提供移动构造函数,Boost.Asio的实现将使用处理程序的移动构造函数而不是其复制构造函数。在某些情况下,Boost.Asio可能能够消除对处理程序的复制构造函数的所有调用。但是,处理程序类型仍然需要是可复制构造的。

当不满足类型要求时,Asio执行的类型检查允许更友好的编译器错误消息。此类型检查在调用堆栈中较早发生,并且不基于对象的使用是否会生成编译器错误。例如,当启用类型检查时,类型检查将为没有复制构造函数的处理程序发出编译器错误,即使已经消除了对处理程序的复制构造函数的所有调用。通过定义BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS,可以禁用显式类型检查,并允许编译器错误从实现中更深层的调用点出现(如果它们发生)。 History注意到此选项:

  

Asio 1.6.0 / Boost 1.47

     
      
  • ...
  •   
  • 在完成处理程序不满足必要的类型要求时添加了更友好的编译器错误。当C ++ 0x可用时(当前支持g++ 4.5或更高版本,以及MSVC 10),static_assert也用于生成信息性错误消息。可以通过定义BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
  • 来禁用此检查   

以下是此功能的完整示例demonstrating。在其中,由unique_ptr管理的套接字的所有权通过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)));
}

答案 1 :(得分:-1)

这只是一个惯例。

该约定可以在许多C ++库中找到,包括所有标准库算法

约定使得

更容易
  • 正确实现语义
  • 知道会发生什么
  • 让编译器进行深度优化(由于别名问题,引用会导致很多优化)

因此,如果您需要通过引用处理程序,只需传递std::ref(f)。它是为此目的而设计的。

谨防终身问题 - 与使用引用时一样。