问题
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 definingBOOST_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