Boost ASIO with CRTP or PBCP or duck-typing

233 views Asked by At

I wanted to write a wrapper over the boost ASIO for a tcp client/server implementation. The interface of boost ASIO is really nice, but the reason to have to wrapper is to be able to replace the event loop with something else. In our usecase, we need to just need invoke the same handler function for every async read, the application doesn't need to pass the handler for every asyncRead call. So it helps to register the handler for once. One way I tried is like this -

template <class Connection>
struct TCPClient { // implements the interface with ASIO

Connection *_connection;

void setConnection (Connection *connection)
{
    _connection = connection;
}

void asyncRead ()
{
    _socket.async_read_some(boost::asio::null_buffers(), 
               [this] (ErrorType err, unsigned a) {

                   if (_connection) _connection->handleRead(err);
                   if (!err) asyncRead();
               });
}

};

I could do the similar with CRTP

class MyConnection : public TCPClient<MyConnection> {
    void readHandler (TCPClient::ErrType err)
    {
    }
};

And in TCPClient class the asyncRead will be

void asyncRead ()
{
    _socket.async_read_some(boost::asio::null_buffers(), 
               [this] (ErrorType err, unsigned a) {
                   ((Connection *)this)->handleRead(err);
                   if (!err) asyncRead();
               });
}

This in case is helpful as the lifetime of the TCPClient and connection is same.

Or PBCP

template <typename Connection>
class TCPClient : public Connection {

void asyncRead ()
{
    _socket.async_read_some(boost::asio::null_buffers(), 
               [this] (ErrorType err, unsigned a) {
                   Connection::handleRead(err);
                   if (!err) asyncRead();
               });
}
};

I dont think actually there is a IS-A relation b/w TCPCLient and Connection. I am confused if any of these scheme is good. (Also I wonder why ASIO doesnt have a scheme where is caches the handler once and calls it everytime. Atleast in case of Async read, normally there is no context to be returned. In our case, rate of reading messages is the greatest concern and Boost ASIO copying the read handler everytime + memory allocation to store is really bad. So based on test results we may have to change the event loop to something custom)

1

There are 1 answers

0
madduci On

I have done some work in this sense. In your CRTP Base class you can try to create a template-parametrized method which calls the derived class and sets a std::function holding a lamba, which needs to be passed to the async_read/write.

Base class:

template <class Connection>
struct TCPClient { // implements the interface with ASIO

std::function<void(const boost::system::error&) handler{};

void registerConnectionHandler(std::function<void(const boost::system::error&)> &&impl)
{
    static_cast<MyConnection*>(this)->registerConnectionHandler(std::forward<decltype(impl)>(impl));
}    

void asyncRead ()
{
   _socket.async_read_some(boost::asio::null_buffers(), handler);
}

};

In the derived class:

class MyConnection : public TCPClient<MyConnection> {
public:
    void registerConnectionHandler(std::function<void(const boost::system::error&)> &&impl)
    {
      handler = std::move(impl);
    }
};    

Another way to do it is to implement the handler in the derived class, without using the registerConnectionHandler with a std::function, which is probably the best way to do it:

class MyConnection : public TCPClient<MyConnection> {
public:
    void registerConnectionHandler()
    {
      handler =  [this](const boost::system::error &err)
      {
         // your stuff here
      };
    }
};