为什么 Boost.Asio 处理程序必须是可复制构造的?
why must a Boost.Asio handler be copy constructible?
根据 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
这只是一个约定。
许多 C++ 库中都可以找到该约定,包括所有标准库算法。
约定使得
- 正确实现语义
- 知道会发生什么
- 让编译器进行深度优化(由于别名问题,引用终止了许多优化)
因此,如果您需要引用处理程序,只需传递 std::ref(f)
。它是为此目的而设计的。
注意生命周期问题 - 使用引用时一如既往。
我无法找到任何 material 来解释为什么需要处理程序 CopyConstructible
,但即使在 C++11 支持的情况下也明确指出了这一点。 Movable Handlers 文档说明:
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.
当不满足类型要求时,Asio 执行的类型检查允许更友好的编译器错误消息。此类型检查发生在调用堆栈的较早位置,并且不以使用该对象是否会产生编译器错误为前提。例如,当启用类型检查时,类型检查将为没有复制构造函数的处理程序发出编译器错误,即使对处理程序的复制构造函数的所有调用都已消除。通过定义 BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
,可以禁用显式类型检查,并允许编译器错误在发生时从实现中更深层的调用点浮出水面。 History 注释此选项:
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
.
这里是一个完整的例子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)));
}
根据 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
这只是一个约定。
许多 C++ 库中都可以找到该约定,包括所有标准库算法。
约定使得
- 正确实现语义
- 知道会发生什么
- 让编译器进行深度优化(由于别名问题,引用终止了许多优化)
因此,如果您需要引用处理程序,只需传递 std::ref(f)
。它是为此目的而设计的。
注意生命周期问题 - 使用引用时一如既往。
我无法找到任何 material 来解释为什么需要处理程序 CopyConstructible
,但即使在 C++11 支持的情况下也明确指出了这一点。 Movable Handlers 文档说明:
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.
当不满足类型要求时,Asio 执行的类型检查允许更友好的编译器错误消息。此类型检查发生在调用堆栈的较早位置,并且不以使用该对象是否会产生编译器错误为前提。例如,当启用类型检查时,类型检查将为没有复制构造函数的处理程序发出编译器错误,即使对处理程序的复制构造函数的所有调用都已消除。通过定义 BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
,可以禁用显式类型检查,并允许编译器错误在发生时从实现中更深层的调用点浮出水面。 History 注释此选项:
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 definingBOOST_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)));
}