std::thread构造时参数默认值传递,引用会被decay为副本,需用std::ref或std::cref显式包装左值引用以实现真正引用传递,否则导致编译错误或未定义行为。

std::thread 构造时参数默认是值传递
直接把引用变量传给 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 引用 - 右值(如临时对象)无法安全引用传递,应避免
std::ref 传引用的实际写法
下面这段代码演示如何让子线程真正修改主线程的变量:
int value = 42;
std::thread t([](int& v) {
v *= 2; // 修改原始变量
}, std::ref(value));
t.join();
// 此时 value == 84注意点:
立即学习“C++免费学习笔记(深入)”;
-
std::ref(value)必须在t.join()或t.detach()前保持有效;如果value在线程运行期间被销毁(比如它是个局部变量而线程 detach 了),就是悬垂引用,UB - lambda 参数必须声明为引用类型(
int& v),否则std::ref的效果被忽略 - 不能对字面量或临时量用
std::ref:std::ref(100)是非法的
值传递 vs 引用传递的性能与语义差异
传大对象(如 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::thread 构造函数模板使用完美转发(std::forward),但前提是参数本身得能被转发。而 std::ref 返回的是 std::reference_wrapper 类型,它重载了函数调用操作符,能在被解包时还原成引用语义。
换句话说:
- 直接传 x → 转发的是 x 的拷贝
- 传 std::ref(x) → 转发的是一个可被隐式转为 T& 的 wrapper,最终 lambda 拿到的是真正的引用
这也是为什么不能混用:比如 std::thread{f, std::ref(a), b},其中 a 是引用语义,b 是值语义,各自独立生效。
线程启动后,参数绑定就固定了。后续对原变量的修改是否可见,只取决于你当初传的是什么——这点很容易被忽略,尤其在调试竞态时。











