为何std::thread按值传参?Stroustrup的解释是否有误?
关于std::thread传参与Stroustrup表述的解释
核心问题解答
是,std::thread按值传递参数的唯一核心原因就是C++标准明确要求:对传递给线程构造函数的所有参数应用std::decay处理,这并非可变参数模板本身的限制,而是线程库为避免异步安全问题做出的设计规定。
对Stroustrup表述的解释
Bjarne Stroustrup提到线程构造函数是可变参数模板,但传递引用必须用引用包装器,本质是因为线程的异步执行特性:
- 线程创建后,其执行时机不确定,可能在当前线程的局部变量销毁后才开始运行。如果直接转发引用,极易出现悬垂引用(引用指向已销毁的对象)。
- 为了避免这种风险,C++标准规定:线程构造函数会先对每个传入的参数执行
std::decay转换——这会把引用类型转换成对应的值类型、把数组转换成指针、去掉cv限定符等,再将这些衰变后的副本存储在线程内部,供线程函数使用。
结合你给出的代码例子来看:
void my_task(vector<double>& arg); void test(vector<double>& v) { thread my_thread1 {my_task,v}; //oops: pass a copy of v thread my_thread2 {my_task,ref(v)}; // OK: pass v by reference thread my_thread3 {[&v]{ my_task(v); }}; // OK: dodge the ref() problem // ... }
my_thread1直接传v:std::decay<vector<double>&>的结果是vector<double>,所以会拷贝一份v传递给线程函数,原引用参数arg绑定的是这个副本,而非原v。my_thread2用ref(v):std::ref返回std::reference_wrapper<vector<double>>类型,std::decay作用于这个类型时不会改变它,线程函数最终会通过引用包装器拿到原v的引用。my_thread3的lambda捕获&v:lambda内部直接使用原v的引用,绕开了线程构造函数的参数衰变逻辑,相当于在lambda层面手动维护了引用的生命周期(前提是v的生命周期长于线程)。
与完美转发例子的区别
你给出的完美转发代码:
void g(int& t) { } template <class... T> void f(T&&... t) { g(std::forward<T>(t)...); } int main() { int i; f(i); return 0; }
这个场景是同步执行:函数f在当前线程直接调用g,参数的生命周期完全可控,不存在异步导致的悬垂风险,所以可以安全地用完美转发传递引用。这和std::thread的异步执行场景本质不同,因此两者的参数处理逻辑也不一样。
内容的提问来源于stack exchange,提问作者alex35833
相关产品推荐
相关产品推荐

