I'm trying to use boost::asio::io_service::post with std::unique_ptr in lambda-capture of the handler. But the post handler needs to be copy-constructed for later execution. Thus, as far as I understand the msgPtr value of type std::unique_ptr in the lambda-capture as well needs to be copied, even though I'm trying to move-construct it in the lambda-capture.
But the copy constructor in the unique_ptr is deleted, which makes complete sense.
Is there a way to move the std::unique_ptr to be executed later in the handler of the io_service::post?
The error I get when I try to compile it with GCC:
error: use of deleted function ...
BOOST_ASIO_COMPLETION_HANDLER_CHECK(CompletionHandler, handler) type_check;
The code sample goes below (updated):
#include <boost/asio.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <deque>
#include <memory>
#include <string>
// Defined abstract class IMsg and concrete MsgTest class, representing the
// messages to be sent
class IMsg {
public:
explicit IMsg() = default;
virtual ~IMsg() = default;
IMsg(const IMsg& msg) = delete;
IMsg& operator=(const IMsg& msg) = delete;
virtual const std::string& Serialize() = 0;
};
class MsgTest : public IMsg {
public:
explicit MsgTest(const std::string& msg) : m_testMsg(msg) {}
MsgTest(const MsgTest&) = delete;
MsgTest& operator=(const MsgTest&) = delete;
const std::string& Serialize() override { return m_testMsg; }
private:
std::string m_testMsg;
};
using IMsgPtr = std::unique_ptr<IMsg>;
class MsgClient {
public:
MsgClient(boost::asio::io_service& ioService)
: m_ioService(ioService), m_socket(ioService) {}
void asyncSendMsg(IMsgPtr&& msgPtr) {
m_ioService.post([this, msgPtr{std::move(msgPtr)}]() mutable {
m_messagesOut.emplace_back(std::move(msgPtr));
// Serialize() returns const reference to serialized member data.
// Thus, the data will be valid until the async_write returns.
const std::string& currentMsg = m_messagesOut.front()->Serialize();
// todo: such usage of async_write is dangerous and not thread-safe!
boost::asio::async_write(
m_socket, boost::asio::buffer(currentMsg, currentMsg.length()),
[this](boost::system::error_code ec, std::size_t length) {
if (ec == 0) {
m_messagesOut.pop_front();
} else {
// todo: error
}
});
});
}
private:
std::deque<IMsgPtr> m_messagesOut;
boost::asio::io_service& m_ioService;
boost::asio::ip::tcp::socket m_socket;
};
// Calling the asynSendMsg
int main() {
boost::asio::io_service ioServiceWorker;
MsgClient client(ioServiceWorker);
//todo: client.connect()...
client.asyncSendMsg(std::make_unique<MsgTest>("Hello World"));
// code that runs io_service in a separate thread is present below
// but omitted.
}
P.S. We are limited to using C++14 only and Boost.Asio version 1.57. Also, if anyone is interested in why I'm using post and call async_write in the handler (why not async_write directly):
I have a multi-threaded app, and asyncSendMsg is usually called from a different thread than the MsgClient resides in. Thus, by adding the IMsgPtr into the array by post-ing, I can avoid using inter-thread synchronization using mutexes - as far as post's handler will always be executed in the same thread where the MsgClient is located.
I'd appreciate knowing how I can move construct std::unique_ptr<IMsg> in the post's handler.
Also, if you have any ideas on improving the code quality here, this will be appreciated as well.