Reputation: 217
I have a class X that takes a reference to boost::asio::io_service
and a connected boost::astio::ip::tcp::socket
in its constructor. That class handles sending and receiving of network data.
One issue I'm having is that the main code asks X to send a message X::sendMessage()
and then the main code deletes X on the very next line. So, X calls boost::asio::async_write
, using a lambda as a handler, then X gets deleted, its destructor is called, which closes the socket. There is no X anymore. But after a while boost::asio::io_service
calls my lambda handler used in the boost::asio::async_write
call, which is now in a destructed class, with boost::system::error_code
set to "Success".
Is there some way to tell the lambda that the class was destructed and it shouldn't mess with class'es members and methods?
Maybe I can somehow cancel the lambda handler from being called in ~X()
? Though the write operation could have already completed and scheduled the handler to be executed by boost::asio::io_service
, so there is nothing really to cancel.
Note that I can't do anything with the boost::asio::io_service
object, as it is passed to X by the main code and it handles much more than just networking.
class X
{
public:
X(boost::asio::io_service &io, boost::asio::ip::tcp::socket socket)
: io(io), socket(std::move(socket)){ }
~X()
{
if (socket.is_open()) {
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
socket.close();
}
}
void X::sendMessage()
{
boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
[this](boost::system::error_code ec, std::size_t /*length*/)
{
std::cout << ec.message() << std::endl; // Success!
// `this' is invalid though
if (!ec) {
// code
} else {
// code
}
});
}
private:
boost::asio::io_service &io;
boost::asio::ip::tcp::socket socket;
Edit:
For now I just created a shared pointer with a bool, which I pass to lambdas and which gets set to true in the destructor, so that lambdas could tell if the object was already destructed. It's a dirty hack, but it works for now. I would like to have a more elegant solution in the long run though.
Upvotes: 1
Views: 375
Reputation: 136425
If you would like to make sure that your object stays alive while asio
framework has references to it, pass a shared ownership smart pointer to the object into the lambda expression. E.g.:
class X : public enable_shared_from_this<X>
// ...
boost::shared_ptr<X> that = this->shared_from_this();
boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
[that](boost::system::error_code ec, std::size_t /*length*/)
Upvotes: 1
Reputation: 8660
A solution is to declare the X class member as a shared pointer (i.e. boost::shared_ptr<X> m_x;
) and pass the class instance to the lambda. See the modified code below.
It prevents the class instance destruction until Asio calls the lambda.
#include <boost/noncopyable.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
class X : private boost::noncopyable, public boost::enable_shared_from_this<X>
{
private:
struct Data {
char *data;
size_t size;
};
Data m;
public:
X(boost::asio::io_service &io, boost::asio::ip::tcp::socket socket)
: io(io), socket(std::move(socket)){ }
~X()
{
if (socket.is_open()) {
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
socket.close();
}
}
void X::sendMessage()
{
auto self = shared_from_this();
boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
[self](boost::system::error_code ec, std::size_t /*length*/)
{
std::cout << ec.message() << std::endl; // Success!
// `this' is invalid though
if (!ec) {
// code
}
else {
// code
}
});
}
private:
boost::asio::io_service &io;
boost::asio::ip::tcp::socket socket;
};
Upvotes: 0