2

当我运行此代码时,我得到 std::bad_functon_call 异常。我无法弄清楚这个异常的原因是什么。它是由receiveCallback 中的async_receive 抛出的。receiveCallback 在被调用之前是否从内存中清除?

//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));
4

1 回答 1

2

您的代码是未定义的行为:当 lambdareceiveCallback按值捕获时,receiveCallback尚未初始化,因此 lambda 获取的副本是垃圾(使用 GCC 4.7,我什至得到一个 segfault 而不是std::bad_function_call)。acceptCallback顺便说一句,表现出同样的问题。

一个明显的解决方案是receiveCallback通过引用而不是通过值来捕获,但这会带来与对象生命周期相关的问题(receiveCallback对象必须在整个 I/O 操作期间保持活动状态,但它会很快被销毁当你退出acceptlambda 时)。

这是一种先有鸡还是先有蛋的情况:由于生命周期问题,按引用捕获将不起作用,您需要按值捕获来解决这个问题,但是按值捕获会复制未初始化的对象,因此您需要按引用捕获来解决这个问题。嗯。

请注意,这receiveCallback是不正确的另一种方式:您也按值捕获message,这意味着每当调用 lambda 时,您最终不会得到由 填充的缓冲区boost::asio,而是在实例化 lambda 时创建的缓冲区副本(换句话说,又是未初始化的垃圾)。


既然做出了诊断,如何解决这个问题并编写工作代码?

在我看来,完全放弃 lambdas。编写一个类来保存您的message缓冲区和onMessageReceivedCallback,并且具有acceptreceive成员函数。

根据您的确切 API,boost::asio您可能需要将您的成员函数“包装”在std::function对象中以获取asio可以使用的回调(std::mem_fn这里是您的朋友,不要忘记std::bind将结果对象的第一个参数std::function传递给您的类的实例)。

于 2013-05-20T05:59:48.540 回答