使用boost::bind绑定std::function时的编译问题及解决咨询
咱们先把问题拆明白,你之所以编译报错,核心是混淆了成员函数指针和std::function成员变量的本质:
错误原因
- 原来的
_rx_handler是类的成员函数,&udp_server::_rx_handler是一个合法的成员函数指针,boost::bind能正确处理这种指针,把this和参数绑定起来,所以之前的代码能正常工作。 - 现在
_rx_handler变成了std::function类型的成员变量——它是一个装着可调用对象的容器,不是成员函数指针。你还在用boost::bind(&udp_server::_rx_handler, this, ...)的写法,相当于让bind去绑定这个成员变量的地址(也就是指向std::function的成员指针),而不是调用这个std::function对象,这完全不符合bind的预期,编译器自然会报错。
至于你尝试的target()方法,std::function::target()是个模板成员函数,必须指定目标类型才能调用(比如_rx_handler.target<void(const boost::system::error_code&, std::size_t, int)>()),但就算拿到了内部的函数指针,这种用法也没必要——std::function本身就是可调用对象,直接用就行。
解决方法
既然_rx_handler是std::function类型的成员变量,咱们只需要把它作为可调用对象,和asio的占位符、额外参数绑定起来就行。这里有两种简洁的写法:
方法1:用boost::bind直接绑定std::function对象
std::function本身是可调用的,你可以直接把它传给boost::bind,不用再取成员指针:
void start_receive() { _socket.async_receive_from( boost::asio::buffer(_buffer), _remote_endpoint, boost::bind(_rx_handler, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, 42) ); }
注意:如果
_rx_handler可能在回调触发前被修改或销毁,建议用std::ref(_rx_handler)传递引用,但在UDP服务器场景下,只要服务器对象生命周期足够,直接传就行。
方法2:用C++11 Lambda(更直观,强烈推荐)
Lambda表达式的语法比bind清晰太多,还能方便加空指针检查,避免_rx_handler未赋值就调用的问题:
void start_receive() { _socket.async_receive_from( boost::asio::buffer(_buffer), _remote_endpoint, [this](const boost::system::error_code& ec, std::size_t bytes_rx) { if (_rx_handler) { // 先确认std::function已被赋值 _rx_handler(ec, bytes_rx, 42); } } ); }
这种写法不仅可读性高,出错概率也低,现在C++11及以后的项目里,Lambda基本已经替代了复杂的bind用法。
额外解惑
你之前觉得普通函数和std::function可以互换,其实准确来说是:std::function可以存储普通函数、bind包装的成员函数、Lambda等各种可调用对象,但std::function本身是个类对象,不是函数指针,所以不能用绑定成员函数指针的方式去绑定它。
换成Lambda之后,你在子类里给_rx_handler赋值的逻辑完全不受影响,比如子类里可以这样写:
class my_udp_server : public udp_server { void my_handler(const boost::system::error_code& ec, std::size_t bytes_rx, int val) { // 子类的处理逻辑 } public: my_udp_server() { assignCallback([this](const auto& ec, auto bytes_rx, auto val) { my_handler(ec, bytes_rx, val); }); } };
这样就能完美实现你想要的“子类自定义处理逻辑”的需求。
内容的提问来源于stack exchange,提问作者kebs

