Некоторое время назад я опубликовал этот вопрос и принял принятое решение, которое работает хорошо. Теперь мне нужна версия со стертым типом, чтобы я мог шаблонировать отправителя на основе типа сокета (т. е. шаблона class sender_impl;) для поддержки нескольких транспортов, но хранить их все в одном контейнере.
На основе этого примера я набросал следующее (полное раскрытие, я еще не пытался его скомпилировать), и я хочу убедиться, что я на правильном пути:
#include "asio/buffer.hpp"
#include
#include
#include
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
using boost::system::error_code;
class sender
{
public:
virtual ~sender() = default;
template auto async_send_message(asio::const_buffer buf, Token&& token) {
auto init = [this](auto handler, asio::const_buffer buf) {
do_async_send_message(buf, std::move(handler));
};
return asio::async_initiate(init, std::forward(token), buf);
}
private:
virtual void do_async_send_message(asio::const_buffer buf, asio::any_completion_handler h) = 0;
};
class sender_impl : public sender, public std::enable_shared_from_this {
public:
explicit sender_impl(tcp::socket sock) : _socket(std::move(sock)) {}
private:
template auto do_async_send_message(asio::const_buffer buf, asio::any_completion_handler h) final {
auto wrap = asio::consign(std::move(h), asio::make_work_guard(_socket.get_executor()));
// Initiate the async operation on the socket's strand
asio::dispatch(_socket.get_executor(), //
[this, me = shared_from_this(), buf, h = std::move(h)] mutable {
async_send_message_impl(buf, std::move(h));
});
}
template void async_send_message_impl(asio::const_buffer buf, Handler h) {
_send_queue.emplace_back(buf, std::move(h));
// Queue was empty meaning there's no active send loop, so start one.
if (_send_queue.size() == 1)
send_loop();
}
void send_loop() {
if (_send_queue.empty())
return;
asio::async_write( //
_socket, _send_queue.front().first, [this, me = shared_from_this()](error_code ec, std::size_t) {
auto& h = _send_queue.front().second;
auto const& ex = asio::get_associated_executor(h, _socket.get_executor());
asio::dispatch(ex, [h = std::move(h), ec]() mutable { h(ec); });
if (!ec.failed()) {
// Keep sending until no messages left to send.
_send_queue.pop_front();
send_loop();
}
});
}
using entry = std::pair;
tcp::socket _socket;
std::deque _send_queue;
};
В частности, я вижу, что теперь вызываю asio::make_work_guard() иshared_from_this() внутри функции инициации, что, возможно, имеет последствия для времени жизни.
Я считаю, что это нормально с точки зрения времени жизни, если я предполагаю, что функция инициации вызывается до возврата async_initate(), но этот документ предполагает, что некоторые токены завершения могут выбрать отложить выполнение функции инициации, что предположительно может стать проблемой.
Несколько вопросов:
Правильный ли это подход?
Если нет, то что?
Откладывают ли какие-либо стандартные токены завершения (обратные вызовы, фьючерсы, ожидаемые объекты) функция инициации? Если нет, можете ли вы привести мне пример того, что могло бы быть? (РЕДАКТИРОВАТЬ: Глупый я, я думаю, отложенный является очевидным примером этого? Я наконец-то понимаю, что он делает сейчас...)
Некоторое время назад я опубликовал этот вопрос и принял принятое решение, которое работает хорошо. Теперь мне нужна версия со стертым типом, чтобы я мог шаблонировать отправителя на основе типа сокета (т. е. шаблона class sender_impl;) для поддержки нескольких транспортов, но хранить их все в одном контейнере. На основе этого примера я набросал следующее (полное раскрытие, я еще не пытался его скомпилировать), и я хочу убедиться, что я на правильном пути: [code]#include "asio/buffer.hpp" #include #include #include
namespace asio = boost::asio; using tcp = asio::ip::tcp; using boost::system::error_code;
class sender { public: virtual ~sender() = default;
template auto async_send_message(asio::const_buffer buf, Token&& token) { auto init = [this](auto handler, asio::const_buffer buf) { do_async_send_message(buf, std::move(handler)); };
class sender_impl : public sender, public std::enable_shared_from_this { public: explicit sender_impl(tcp::socket sock) : _socket(std::move(sock)) {}
private: template auto do_async_send_message(asio::const_buffer buf, asio::any_completion_handler h) final { auto wrap = asio::consign(std::move(h), asio::make_work_guard(_socket.get_executor()));
// Initiate the async operation on the socket's strand asio::dispatch(_socket.get_executor(), // [this, me = shared_from_this(), buf, h = std::move(h)] mutable { async_send_message_impl(buf, std::move(h)); }); }
// Queue was empty meaning there's no active send loop, so start one. if (_send_queue.size() == 1) send_loop(); }
void send_loop() { if (_send_queue.empty()) return;
asio::async_write( // _socket, _send_queue.front().first, [this, me = shared_from_this()](error_code ec, std::size_t) { auto& h = _send_queue.front().second; auto const& ex = asio::get_associated_executor(h, _socket.get_executor()); asio::dispatch(ex, [h = std::move(h), ec]() mutable { h(ec); });
if (!ec.failed()) { // Keep sending until no messages left to send. _send_queue.pop_front(); send_loop(); } }); }
using entry = std::pair; tcp::socket _socket; std::deque _send_queue; }; [/code] В частности, я вижу, что теперь вызываю asio::make_work_guard() иshared_from_this() внутри функции инициации, что, возможно, имеет последствия для времени жизни. Я считаю, что это нормально с точки зрения времени жизни, если я предполагаю, что функция инициации вызывается до возврата async_initate(), но этот документ предполагает, что некоторые токены завершения могут выбрать отложить выполнение функции инициации, что предположительно может стать проблемой. Несколько вопросов: [list] [*]Правильный ли это подход?
[*]Если нет, то что?
[*]Откладывают ли какие-либо стандартные токены завершения (обратные вызовы, фьючерсы, ожидаемые объекты) функция инициации? Если нет, можете ли вы привести мне пример того, что могло бы быть? (РЕДАКТИРОВАТЬ: Глупый я, я думаю, отложенный является очевидным примером этого? Я наконец-то понимаю, что он делает сейчас...)