2013-05-20 2 views
2

Когда я запускаю этот код, я получаю исключение std :: bad_functon_call. Я не могу понять, в чем причина этого исключения. Он вызывается async_receive внутри receiveCallback. Получается, что получатель получает отказ от памяти перед вызовом?C++ lambda self-pass exception

//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)); 
+0

Хотя легкость чтения и упорядочивания лямбда может быть приятной, часто бывает трудно из-за времени жизни. Возможно, стоит рассмотреть [coroutines] (http://stackoverflow.com/a/13997290/1053968) в качестве альтернативы. –

ответ

2

Ваш код Неопределенное поведение: когда лямбда захватывает receiveCallback по значению, receiveCallback еще не инициализирована, поэтому копия лямбда получает мусор (с GCC 4.7 я даже получить Segfault вместо std::bad_function_call). acceptCallback выставляется такая же проблема.

Очевидным решением было бы захватить receiveCallback по ссылке, а не по значению, но тогда это будет создавать проблемы, связанные с временем жизни объекта (receiveCallback объект должен остаться в живых в течение всей продолжительности операций ввода/вывода, но он будет уничтожен, как только вы выйдете из лямбда accept).

Это ситуация с курятиной и яйцами: захват по ссылке не будет работать из-за жизненных проблем, вам нужно захватить по значению, чтобы решить это, но захват по значению делает копию неинициализированного объекта, поэтому вам нужно сделать снимок со ссылкой на решить это. Мех.

Обратите внимание, что receiveCallback неверно по-другому: вы захватить message значением тоже, что означает, что всякий раз, когда лямбда называется вы в конечном итоге не с буфером, заполненного boost::asio, но с копией буфера, который был когда был создан экземпляр лямбда (другими словами, неинициализированный мусор снова).


Теперь, когда диагностика выполнена, как исправить это и написать рабочий код?

По-моему, полностью опустите лямбды. Напишите класс, который будет содержать ваш буфер message и onMessageReceivedCallback, и который имеет accept и receive функции-члены.

В зависимости от конкретного API из boost::asio вам может понадобиться «обернуть» свои функции-члены в std::function объекты, чтобы получить обратные вызовы, которые можно использовать asio (std::mem_fn ваш друг здесь, не забудьте std::bind первый аргумент в результате std::function объект к экземпляру вашего класса).