2

When I run this code, I get std::bad_functon_call exception. I cannot figure out whats the reason for this exception. It's thrown by the async_receive inside receiveCallback. Is receiveCallback cleared out from the memory before being called?

//callback on connection accepted
    std::function<void(const boost::system::error_code& error, tcp::socket* socketPtr)> acceptCallback =
        [this, onMessageReceivedCallback, acceptCallback](const boost::system::error_code& error, tcp::socket* socketPtr)
    {
        cout<<"accept: "<<error.message()<<endl;

        const int bufferSize = 100;
        char* message = new char[bufferSize];

        //callback on message received
        std::function<void(const boost::system::error_code& error,std::size_t bytes_transferred)> receiveCallback =
            [message, bufferSize, socketPtr, onMessageReceivedCallback, receiveCallback](const boost::system::error_code& error,std::size_t bytes_transferred)
        {
            onMessageReceivedCallback(message, bytes_transferred);

            socketPtr->async_receive(
            boost::asio::buffer(message, bufferSize),
            receiveCallback);
        };

        socketPtr->async_receive(
            boost::asio::buffer(message, bufferSize),
            receiveCallback);

        //create socket for the next connection
        socketPtr = new tcp::socket(io_service_);
        //continue accepting connections
        acceptor_.async_accept(*socketPtr, std::bind(acceptCallback, std::placeholders::_1, socketPtr));
Marcin Król
  • 1,555
  • 2
  • 16
  • 31
  • While the lambda readability and ordering can be nice, it is often difficult due to lifetimes. It may be worth considering [coroutines](http://stackoverflow.com/a/13997290/1053968) as an alternative. – Tanner Sansbury May 20 '13 at 12:54

1 Answers1

2

Your code is Undefined Behaviour: when the lambda captures receiveCallback by value, receiveCallback is not yet initialized so the copy the lambda gets is garbage (with GCC 4.7 I even get a segfault instead of std::bad_function_call). acceptCallback exhibits the same problem by the way.

An apparent solution would be to capture receiveCallback by reference instead of by value, but then it will pose problems related to the lifetime of the object (the receiveCallback object must stay alive for the whole duration of the I/O operations, but it gets destroyed as soon as you exit the accept lambda).

It's a chicken and egg situation: capture by reference won't work because of lifetime issues, you need capture by value to solve that, but capture by value makes a copy of an uninitialized object so you need capture by reference to solve that. Meh.

Note that receiveCallback is incorrect in another way: you capture message by value too, which means that whenever the lambda is called you'll end up not with the buffer filled by boost::asio, but with a copy of the buffer that was made when the lambda was instantiated (in other words, uninitialized garbage again).


Now that the diagnostic is made, how to fix that and write working code?

In my opinion, drop the lambdas altogether. Write a class that will hold your message buffer and onMessageReceivedCallback, and that has accept and receive member functions.

Depending on the exact API of boost::asio you may need to "wrap" your member functions in std::function objects to get callbacks that asio can use (std::mem_fn is your friend here, don't forget to std::bind the first argument of the resulting std::function object to the instance of your class).

syam
  • 14,701
  • 3
  • 41
  • 65