Search code examples
c++c++11asynchronouscallbackstd-function

std::function callbacks with asynchronous operations


I want to use std::functions for callback parameters in a wrapper class. The class wraps a library that allows asynchronous TCP/IP operations (actually boost::asio but neither boost::asio nor TCP/IP should matter here, only that it has asynchronous operations).

The library functions allow me to pass another callback function object that is asynchronously called when the requested operation is finished. Depending on the result of the asynchronous operation I want to invoke the callback specified by my client or start further operations. The following code tries to sketch what I intend.

using ConnectHandler = std::function<void(boost::system::error_code ec)>;

class MyConnection
{

public:

    void Connect(ConnectHandler handler);  // (1)
}

void MyConnection::Connect(ConnectHandler handler)
{
    SomeLibrary::async_connect(...,
        [handler](boost::system::error_code ec, ...) // (2)
        {
            //Depending on ec start a nested read/write operation.
            //One way or another it finally invokes the callback
            handler(ec); // (3)
        });
}

The client code would look something like this

MyConnection conn;

conn.Connect([](boost::system::error_code ec)
{
    //check ec and start read/write operation
});

My question is: what is the best way to declare my Connect method in (1), f.e

void Connect(ConnectHandler handler);
void Connect(const ConnectHandler& handler);
void Connect(ConnectHandler&& handler);

and depending on that how do I correctly capture the callback handler in the lambda capture clause in (2) such that I can call it in (3)?

A side note: the clients instance of MyConnection will never go out of scope until all asynchronous operations have completed!


Solution

  • std::function are cheap to move, so taking it by value is acceptable. Taking by && is mostly pointless, as at best is saves a move. And it forces the caller to move, not copy, and maybe the caller wants to copy?

    They are not cheap to copy, so you could consider capturing by move in your callable object.

    In C++14, this is as simple as:

    [handler=std::move(handler)]
    

    as a capture list (generalized capture expressions).

    In C++11 you need to write a custom object to do this.

    struct custom_work {
      ConnectHandler handler;
      void operator()(boost::system::error_code ec, ...) const {
        //Depending on ec start a nested read/write operation.
        //One way or another it finally invokes the callback
        handler(ec); // (3)
      }
    };
    

    then

    SomeLibrary::async_connect(...,
        some_work{std::move(handler)}
    );
    

    which has the disadvantage of moving the code from inline to out of line.