提一处,std::thread 的内部实现中在做类型擦除时用到了 tuple,而且此类型擦除手段堪称经典。
因为 pthread_create 只能接受一个 void* (*)(void*) 类型的函数指针指定线程的入口函数和一个 void* 参数指定要传递给入口函数的参数,所以使用十分强调类型安全的模板来包装这种 void* 满天飞的 C-style API 就绝非易事。
解决方法就是把传给 thread 构造函数的回调对象(不光是函数,还包括仿函数对象、lambda 表达式)和参数包一起打包成一个 tuple,开在堆上。而指向这个 tuple 的指针类型擦除成 void*。
而 pthread_create 的入口函数则不能直接传 std::thread 构造函数的第一个参数,因为类型都不一样。我们的 std::thread 的第一个参数是可以传带有任意参数列表的可调用对象的,但 pthread_create 只能接受 void* (*)(void*),那这个时候就要设成一个 helper 了。它负责将参数包 void* 转回成原来的 tuple 类型,然后从这个 tuple 解包出回调对象、参数包。其中 std::get<0>(tuple) 就是回调对象,给它传 tuple 中剩下的所有参数就可以了,即:
std::get<0>(tuple)(std::get<1>(tuple), std::get<2>(tuple), std::get<3>(tuple), ...)
这是我自己简单实现的一个 pthread 包装类(部分代码):
private: template <typename Callable, typename ... Args, size_t ... I> static void apply(std::tuple<Callable, Args...> & pack, std::integer_sequence<size_t, I...>) std::get<0>(pack)(std::get<I + 1>(pack)...); public: template <typename Callable, typename ... Args> explicit thread(Callable&& fun, Args&& ... args) : th_id() typedef std::tuple<Callable, Args...> fun_args_pack_t; struct helper static void* start_rtn(void * fun_args_pack) fun_args_pack_t * fun_args_pack_p = static_cast<fun_args_pack_t*>(fun_args_pack); try { thread::apply(*fun_args_pack_p, std::make_index_sequence<sizeof...(Args)>()); } catch (...) { delete fun_args_pack_p; throw; delete fun_args_pack_p; return NULL; fun_args_pack_t * fun_args_pack = new fun_args_pack_t( std::forward<Callable>(fun), std::forward<Args>(args)... int err = ::pthread_create(&this->th_id.native_handle, NULL, helper::start_rtn, reinterpret_cast<void*>(fun_args_pack));