C++ boost-asio-network in async callback, which one is better ? using lambda or boost::bind()

Viewed 310

C++ lambda made me confusing.

bind functions for async read.

template<typename T>
class connection
    : public boost::enable_shared_from_this<connection<T>>
    , boost::noncopyable
{
public:
    using err = boost::system::error_code;

protected:
    boost::asio::ip::tcp::socket socket_;
...

// completion function
// read until meet '\n'
size_t on_read_completion(const err& error, size_t bytes) 
{
    if (error) { return 0; }

    bool found = std::find(read_buffer_, read_buffer_ + bytes, '\n') < read_buffer_ + bytes;
    return found ? 0 : 1;
}

// read message
void on_read(const err& error, size_t bytes)
{
    if (!started_) { return; }

    if (error)
    {
        std::cout << "[ERROR] async_read " << error.message();
        stop();
    }

    std::string msg(read_buffer_, bytes);
    // handle received message
    on_message(msg);
}

This is async read() function.

void read()
{
    if (!socket_.is_open())
    {
        std::cout << "[ERROR] socket_ is not open\n";
        return;
    }

    std::fill_n(read_buffer_, MAX_MSG, '\0');

    // it works
#if 0
    async_read(socket_, boost::asio::buffer(read_buffer_),
        boost::bind(&connection::on_read_completion, this->shared_from_this(), _1, _2),
        boost::bind(&connection::on_read, this->shared_from_this(), _1, _2));
#else // not works
    async_read(socket_, boost::asio::buffer(read_buffer_),
        [this](const err& error, size_t bytes)->size_t
        {
            if (error) { return 0; }
            bool found = std::find(read_buffer_, read_buffer_ + bytes, '\n') < read_buffer_ + bytes;                return found ? 0 : 1;
        },
        [this](const err& error, size_t bytes)->void
        {
            if (!started_) { return; }
            if (error)
            {
                std::cout << "[ERROR] async_read\n" << error.message();
                return;
            }

            std::string msg(read_buffer_, bytes);
            on_message(msg);
        });
#endif
    }

The problem is lambda [capture] that is not containing shared_ptr itself. So when it call on_read_completion(), lambda capture this is not pointing shared_from_this() that I want.

I need a wise advice for this. Help me please.

2 Answers

Oh, I found the what is the problem.

...
async_read(socket_, boost::asio::buffer(read_buffer_),
  [this](const err& error, size_t bytes)->size_t
  {
    if (error) { return 0; }
    bool found = std::find(read_buffer_, read_buffer_ + bytes, '\n') < read_buffer_ + bytes;
    return found ? 0 : 1;
  },
  [this, self = std::move(this->shared_from_this())](const err& error, size_t bytes)->void
  {
    if (!started_) { return; }
    if (error)
    {
      std::cout << "[ERROR] async_read\n" << error.message() << "\n";
      return;
    }
    std::string msg(read_buffer_, bytes);
    self->on_message(msg);
});

...

it worked!

But if you have tips for this please comment! Thank you.

You already figured out that this needs to be explicitly captured in addition to the shared pointer, the latter just for the lifetime guarantee.

However your original question is slightly more interesting than you knew! There's a subtle difference between lambda and bind expressions. If you use bind to bind to a generic handler, it may preserve asio_handler_invoke (or more modern get_associated_executor) semantics under the ADL rule.

This is rarely a concern at the end-user level, but if you mean to provide a generic library of composable operations you may care about this kind of detail for failing to preserve the invocation semantics could lead to bugs (e.g. when a composed handler is bound to a strand executor).

As a sidenote, in the case you showed, consider using the overload of async_read_until that take a string(view) or regex.

Related