読者です 読者をやめる 読者になる 読者になる

Boost.Asio 送受信バッファをメンバ変数に持たないためのイディオム

C++

Boost.Asioで非同期通信を書いていると、送受信のためのバッファをメンバ変数に持つ必要が出てきて、局所化が難しい場合があります。
この解決策として、以下のようなイディオムを使うことで送受信バッファをローカル変数化することができます:

  1. 送受信バッファをshared_ptrで包む
  2. async_read, async_writeにshared_ptrを間接参照したバッファを渡す
  3. このままでは送受信関数のスコープを抜けた時点でshared_ptrが消えてしまう
  4. 送受信のハンドラにshared_ptrのバッファをbindする
  5. io_serviceは内部でハンドラ関数のキューをメンバ変数として管理してるので、bindされたshared_ptrのバッファはハンドラの呼び出しが終了するまで寿命が尽きない


つまり、メンバ変数の管理を、部分適用されたハンドラとio_serviceで隠してしまうのです。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

namespace asio = boost::asio;

class Client {
    asio::io_service& io_service_;
    asio::ip::tcp::socket socket_;
public:
    Client(asio::io_service& io_service)
        : io_service_(io_service),
          socket_(io_service_)
    {}

    void connect()
    {
        socket_.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.10"), 31400));
    }

    void send()
    {
        boost::shared_ptr<std::string> data(new std::string("abc\n"));
        asio::async_write(socket_, asio::buffer(*data),
            boost::bind(&Client::on_send, this, asio::placeholders::error, data));
    }

private:
    void on_send(const boost::system::error_code& error,
                 boost::shared_ptr<std::string> data)
    {
        if (error) {
            std::cout << "send error: " << error.message() << std::endl;
            return;
        }

        std::cout << "send ok" << std::endl;

        boost::shared_ptr<asio::streambuf> receive_data(new asio::streambuf());
        asio::async_read_until(socket_, *receive_data, '\n',
            boost::bind(&Client::on_receive, this, asio::placeholders::error, receive_data));
    }

    void on_receive(const boost::system::error_code& error,
                    boost::shared_ptr<asio::streambuf> data)
    {
        if (error) {
            std::cout << "receive error: " << error.message() << std::endl;
            return;
        }

        std::cout << "receive ok :" << std::endl;
        std::cout << asio::buffer_cast<const char*>(data->data()) << std::endl;
    }
};

int main()
{
    asio::io_service io_service;
    Client client(io_service);

    client.connect();
    client.send();

    io_service.run();
}

このあたりは、ライブラリをもう一段抽象化することでイディオムではなくライブラリの機能にすることができる気がします。