为什么 boost::asio::io_service 不能用 std::bind 编译?

Why does boost::asio::io_service not compile with std::bind?

我正在尝试使用 g++ 4.9.1 (-std=c++11) 使用 std::threadstd::bindboost::asio 编译简单的测试程序。

但是,当我使用 std::bind 创建新线程时,它不会编译。另一方面,当我切换到 boost::bind 时,一切都很好。

代码如下:

#include <iostream>
#include <memory>
#include <thread>
#include <functional>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

int main(int argc, char* argv[])
{
    boost::asio::io_service ioService;
    std::unique_ptr<std::thread> t;

    t.reset(new std::thread(std::bind(&boost::asio::io_service::run, &ioService)));
    //t.reset(new std::thread(boost::bind(&boost::asio::io_service::run, &ioService)));
    return 0;
}

这是错误:

test.cpp: In function ‘int main(int, char**)’:
test.cpp:12:80: error: no matching function for call to ‘bind(<unresolved overloaded function type>, boost::asio::io_service*)’
    t.reset(new std::thread(std::bind(&boost::asio::io_service::run, &ioService)));
                                                                                            ^
test.cpp:12:80: note: candidates are:
In file included from /usr/include/c++/4.9/memory:79:0,
             from test.cpp:2:
/usr/include/c++/4.9/functional:1623:5: note: template<class _Func, class ... _BoundArgs> typename std::_Bind_helper<std::__or_<std::is_integral<typename std::decay<_Tp>::type>, std::is_enum<typename std::decay<_Tp>::type> >::value, _Func, _BoundArgs ...>::type std::bind(_Func&&, _BoundArgs&& ...)
 bind(_Func&& __f, _BoundArgs&&... __args)
 ^
/usr/include/c++/4.9/functional:1623:5: note:   template argument deduction/substitution failed:
test.cpp:12:80: note:   couldn't deduce template parameter ‘_Func’
    t.reset(new std::thread(std::bind(&boost::asio::io_service::run, &ioService)));
                                                                             ^
In file included from /usr/include/c++/4.9/memory:79:0,
             from test.cpp:2:
/usr/include/c++/4.9/functional:1650:5: note: template<class _Result, class _Func, class ... _BoundArgs> typename std::_Bindres_helper<_Result, _Func, _BoundArgs>::type std::bind(_Func&&, _BoundArgs&& ...)
 bind(_Func&& __f, _BoundArgs&&... __args)
 ^
/usr/include/c++/4.9/functional:1650:5: note:   template argument deduction/substitution failed:
test.cpp:12:80: note:   couldn't deduce template parameter ‘_Result’
    t.reset(new std::thread(std::bind(&boost::asio::io_service::run, &ioService)));
                                                                            ^

我错过了什么?

根据错误消息 boost::asio::io_service::run 被重载或成员模板和 std::bind() 无法确定它要使用哪个重载或实例化。您需要使用类似这样的东西在获取地址时推断出适当的类型,例如,

static_cast<std:size_t (boost::asio::io_service::*)()>(&boost::asio::io_service::run)

关键问题是获取 boost::asio::io_service::run 的地址会导致成员函数有两种选择,但在获取地址时无法确定使用哪一个。只有在使用成员函数时,才明确表示要使用不带附加参数的版本。但是,address-of 运算符不会产生重载集。

为了推断需要哪个重载,std::bind() 需要提供一个自身的重载,该重载具有适当的类型作为其第一个参数。但是,我认为它无法确定该类型应该是什么。我不知道它实际上如何与 std::boost::bind() 一起使用!我 可以 想象 boost::bind() 对没有参数的成员函数有特殊的重载,并且可能对 boost::error_code 有一个重载,但我真的不知道这会有什么帮助.

不过,使用 lambda 函数可能更容易:

std::thread t([&](){ ioService.run(); });
t.join(); // without a joining or detaching terminate() is called

理解这个错误的关键是unresolved overloaded function type部分

这意味着编译器无法确定要使用哪个 boost::asio::io_service::run 的重载版本。

the docs 中查找,看到有 2 个版本,您想使用 std::size_t run() 一个。为了将其传达给编译器,我们需要 static_cast 函数指针指向重载变体的显式类型,此处 std:size_t (boost::asio::io_service::*)()

因此我们写 static_cast<std::size_t (boost::asio::io_service::*)(void)>(&boost::asio::io_service::run) 代替 &boost::asio::io_service::run

完整代码如下所示

boost::asio::io_service ioService;
std::unique_ptr<std::thread> t;

t.reset(new std::thread(std::bind(
    static_cast<std::size_t(boost::asio::io_service::*)(void)>(&boost::asio::io_service::run),
    &ioService
)));

Boost Bind 支持智能指针绑定为绑定成员函数的 this 参数。

这是 std::bind 和 boost::bind 之间的一个相当大(而且很棒)的区别。

Boost Asio 一直提倡在很大程度上依赖于绑定到 shared_pointer<T> 的模式¹,其中 T 可以是异步 IO 上下文中具有 "magically managed" 生命周期的任何东西,例如 connectionclientsessiontransfer

当然,c++11 lambdas 可以直接支持相同的功能(通过复制捕获共享指针)。


¹ 例如

  • boost asio deadline_timer async_wait(N seconds) twice within N seconds cause operation canceled
  • Proper cleanup with a suspended coroutine

错误消息表明 std::bind() 无法确定要使用哪个 io_service::run() 重载:

std::size io_service::run();
std::size io_service::run(boost::system::error_code&);

对于这种特殊情况,Boost.Bind 没有问题,但它确实为 binding an overloaded functions 提供了一些故障排除方法。它建议铸造:

std::bind(
  static_cast<std::size_t (boost::asio::io_service::*)()>(&boost::asio::io_service::run),
  &ioService);

或者使用临时变量:

std::size_t (boost::asio::io_service::*run)() = &boost::asio::io_service::run;
std::bind(run, &ioService);

std::bind() 而不是 boost::bind() 需要显式转换的原因是实现细节。如果对 bind() 的调用的数量没有对被绑定函数的类型施加约束,那么重载函数将需要显式转换。

例如,考虑使用可变参数模板的情况:

template<class F, class... BoundArgs>
unspecified std::bind(F&& f, BoundArgs&&... bound_args);

When the best matching std::bind() overload is being selected, the arity of the call to std::bind() places no restrictions on F.因为 F 可能是以下之一:

  • std::size_t (boost::asio::io_service::*)()
  • std::size_t (boost::asio::io_service::*)(boost::system::error_code&)

表达式 &boost::asio::io_service::run() 不明确。

另一方面,Boost.Bind 是通过重载函数实现的,其中对 boost::bind() 的调用的数量限制了被绑定函数的数量。它的接口 synopsis 列出了以下值得注意的重载:

// 2 args: member-to-function (arity:0), instance
template <class R, class T, class A1>
unspecified bind(R (T::*f)(), A1 a1);

// 3 args: member-to-function (arity:1), instance, arg1
template <class R, class T, class B1, class A1, class A2>
unspecified bind(R (T::*f)(B1), A1 a1, A2 a2);

请注意,当 boost::bind() 有:

  • 元数为 2,指向成员函数的指针的元数为 0
  • 元数为 3,指向成员函数的指针的元数为 1

因此,调用时:

boost::bind(&boost::asio::io_service::run, &ioService)

作为潜在匹配项的 boost::bind() 重载的元数为 2,因此指向成员函数的指针必须是元数为 0 的函数类型。因为集合中只有一个函数io_service::run() 个重载的元数为 0,调用没有歧义。