使用std::thread时,为何允许传递捕获引用的lambda表达式?
我对cppreference上关于std::thread的说明存在疑问:线程函数的参数会按值移动或复制,若需传递引用参数,必须用std::ref或std::cref包装。这是因为无法保证参数在线程执行期间始终存活,线程应拥有参数所有权。
但如下代码中,直接将mainThreadVariable作为引用参数传给tellValue函数,Clang编译器不接受,这符合预期;而使用捕获引用的lambda表达式作为线程函数时,编译器却接受,且能正确修改mainThreadVariable的值,程序输出正常。为何后者是被允许的?
代码示例1(编译失败)
#include <iostream> #include <thread> void tellValue(int& value) { std::cout << "The value is: " << value << std::endl; } int main() { int mainThreadVariable{0}; std::thread thr0{tellValue, mainThreadVariable}; thr0.join(); return 0; }
代码示例2(编译通过且运行正常)
#include <iostream> #include <thread> int main() { int mainThreadVariable{0}; std::thread thr0{[&]() { std::cout << "The value is: " << mainThreadVariable << std::endl; ++mainThreadVariable; }}; thr0.join(); std::cout << "The value is: " << mainThreadVariable << std::endl; return 0; }
程序输出
./PassingArgumentsToAThread The value is: 0 The value is: 1
这两种情况的核心区别在于参数传递机制和编译器的检查逻辑:
代码示例1的报错原因:
std::thread的构造函数会把传入的参数按值复制/移动到线程内部存储,再尝试将这些副本传递给目标函数tellValue。但tellValue需要的是int&类型的左值引用,线程存储里的副本是右值,无法绑定到非const左值引用上——这就是编译器报错的直接原因。用std::ref包装的本质是把变量引用封装成可复制的对象,线程拿到后解引用出原始引用传递给函数,才能匹配类型要求。代码示例2合法的原因:
lambda捕获引用是让lambda内部代码直接绑定到原始变量mainThreadVariable的引用上。此时std::thread构造函数接收的是lambda对象本身(按值复制这个lambda),而lambda对象里保存的是对原始变量的引用——这和直接传递参数给线程函数的逻辑完全不同:线程没有复制mainThreadVariable,而是lambda带着引用“绑定”了原始变量。
编译器允许这种写法,是因为它只负责检查语法和类型合法性,不会替你判断引用的生命周期是否安全。这个例子里调用了thr0.join(),主线程会等待子线程执行完毕再继续,所以mainThreadVariable在子线程运行期间肯定存活,程序能正常运行。但如果改成thr0.detach(),子线程可能在主线程退出后仍运行,此时引用会变成悬垂引用,行为未定义——编译器不会阻止这种错误,生命周期安全需要开发者自己保证。
简单总结:std::thread处理普通参数时强制复制/移动,类型不匹配就报错;lambda捕获引用是开发者主动选择的绑定,编译器只确保语法正确,风险由开发者承担。
内容的提问来源于stack exchange,提问作者Emiliano Toledo

