std::thread构造时参数默认值传递,引用会被decay为副本,需用std::ref或std::cref显式包装左值引用以实现真正引用传递,否则导致编译错误或未定义行为。
直接把引用变量传给 std::thread 构造函数,编译会失败或行为未定义——因为 std::thread 内部会对所有参数调用 std::decay_t,自动剥离引用和 const 限定,变成纯值拷贝。哪怕你写的是 int&,传进去的也是副本。
常见错误现象:
- 编译报错: error: use of deleted function 'std::thread::thread(...)' (尤其传入含非拷贝构造类型的引用时)
- 程序看似运行,但主线程修改了变量,子线程看不到变化(因为操作的是副本)
正确做法是显式包装:
std::ref(x) 包装左值引用(要求 x 的生命周期必须长于线程)std::cref(x) 包装 const 引用下面这段代码演示如何让子线程真正修改主线程的变量:
int value = 42;
std::thread t([](int& v) {
v *= 2; // 修改原始变量
}, std::ref(value));
t.join();
// 此时 value == 84注意点:
std::ref(value) 必须在 t.join() 或 t.detach() 前保持有效;如果 value 在线程运行期间被销毁(比如它是个局部变量而线程 detach 了),就是悬垂引用,UBint& v),否则 std::ref 的效果被忽略std::ref: std::ref(100) 是非法的传大对象(如 std::vector、自定义类)时,值传递会触发完整拷贝,开销明显;引用传递避免拷贝但引入生命周期管理责任。
典型场景对比:
std::cref(v) + lambda 参数 const std::vector& v
std::ref(v) + std::vector& v ,且确保 v 不会在子线程结束前析构std::move(v)),但注意 std::move 后主线程不能再用该对象误用 
std::ref 是 C++ 多线程中最隐蔽的 bug 来源之一——它不报错,只悄悄让你的程序在某些负载下崩溃或结果错乱。
std::thread 构造函数模板使用完美转发(std::forward(args)...),但前提是参数本身得能被转发。而 std::ref 返回的是 std::reference_wrapper 类型,它重载了函数调用操作符,能在被解包时还原成引用语义。
换句话说:
- 直接传 x → 转发的是 x 的拷贝
- 传 std::ref(x) → 转发的是一个可被隐式转为 T& 的 wrapper,最终 lambda 拿到的是真正的引用
这也是为什么不能混用:比如 std::thread{f, std::ref(a), b},其中 a 是引用语义,b 是值语义,各自独立生效。
线程启动后,参数绑定就固定了。后续对原变量的修改是否可见,只取决于你当初传的是什么——这点很容易被忽略,尤其在调试竞态时。