线程函数中万能引用的使用合理性及相关技术疑问
首先咱们先解决你遇到的编译错误:VS2017报的找不到匹配重载,核心原因是重载的MyThreadFunc导致模板参数推导歧义。当你直接传入MyThreadFunc时,编译器无法确定要推导的FunctionType是void(CObject&)还是void(CObject&&),得先帮它消除歧义,比如显式指定模板参数,或者用lambda包裹调用(后面会给示例)。
接下来逐个解答你的疑问:
1)传入右值引用时调用MyThreadFunc(CObject&&)的尝试是否永远无法实现?
当然不是!只是需要解决模板推导的歧义问题。线程构造函数确实会先复制/移动参数到线程内部存储,但完美转发的逻辑是能保留参数的原始值类别的,只要编译器能正确匹配到对应的重载函数。
比如你可以显式指定StartDetachedThread的模板参数,告诉编译器要选哪个重载:
StartDetachedThread<void(CObject&&)>(MyThreadFunc, std::move(object2));
或者用lambda来明确调用右值版本,这样编译器就能直接推导lambda的类型,不会有歧义:
StartDetachedThread([](CObject&& obj) { MyThreadFunc(std::move(obj)); }, std::move(object2));
这样就能成功触发MyThreadFunc(CObject&&)的调用了。
2)将StartDetachedThread声明为StartDetachedThread(FunctionType&& func, Args&&... args)是否有意义?FunctionType后的&&是否多余?
非常有意义,一点都不多余!这里的&&是万能引用(当FunctionType是模板参数时),作用是完美转发传入的函数对象本身。
比如如果传入的是一个捕获了大内存对象的lambda,或者是一个可移动的函数对象(比如std::packaged_task),用FunctionType&&可以让我们直接移动这个函数对象到线程的lambda里,而不是做一次不必要的复制。如果去掉&&,不管传入的是左值还是右值函数对象,都会触发复制操作,在某些场景下会带来性能损耗,甚至无法编译(比如函数对象不可复制时)。
3)在这类线程启动场景中使用Args&&是否有价值,还是应该始终使用Args?
使用Args&&的价值非常大,绝对比直接用Args更合理!
Args&&作为万能引用,能完美转发参数的原始值类别:
- 当你传入左值(比如
object)时,Args会推导为CObject&,std::forward会把它转发为左值引用,线程构造函数会复制这个左值; - 当你传入右值(比如
std::move(object2))时,Args会推导为CObject,std::forward会把它转发为右值引用,线程构造函数会直接移动这个对象,避免了不必要的内存拷贝,提升效率。
如果直接用Args,不管你传入左值还是右值,模板都会推导为值类型,参数会被复制两次:一次是传入StartDetachedThread时的复制,一次是线程构造函数的复制。而用Args&&配合std::forward,就能把复制的次数降到最少(右值甚至可以只移动一次)。
最后给你修正后的可编译代码示例,同时解决了原代码中[&]捕获可能导致的悬空引用问题:
#include <thread> #include <utility> #include <tuple> #include <functional> class CObject {}; void MyThreadFunc(CObject&) {} void MyThreadFunc(CObject&&) {} template<typename FunctionType, typename ...Args> void StartDetachedThread(FunctionType&& func, Args&&... args) { std::thread([func = std::forward<FunctionType>(func), args = std::make_tuple(std::forward<Args>(args)...)]() mutable { std::apply(std::forward<FunctionType>(func), std::move(args)); }).detach(); } int main() { CObject object; StartDetachedThread([](CObject& obj) { MyThreadFunc(obj); }, object); CObject object2; StartDetachedThread([](CObject&& obj) { MyThreadFunc(std::move(obj)); }, std::move(object2)); return 0; }
内容的提问来源于stack exchange,提问作者Wad

