std::thread构造时默认拷贝所有参数,即使传右值或std::move;需用std::move包裹且类型支持移动构造才能真正转移所有权,传引用必须用std::ref。

std::thread 构造时传参会拷贝还是移动
默认情况下,std::thread 构造函数会对所有参数做**拷贝**,哪怕你传的是右值或 std::move 包裹的变量。这不是 bug,是标准行为——因为线程可能在原作用域结束后才真正执行,必须保证参数生命周期独立。
常见错误现象:std::thread t(func, std::move(obj)); 看起来移动了,实际仍调用 obj 的拷贝构造;如果 obj 不可拷贝(比如含 std::unique_ptr),编译直接报错:use of deleted function。
- 想真正转移所有权,得用
std::move包裹参数,且目标类型必须支持移动构造(如std::string、std::vector、自定义可移动类) - 若参数是原始指针或引用,注意:传引用需用
std::ref(x),否则会被当作值传递;传裸指针则不涉及拷贝/移动,但你要自己管好所指对象的生命周期 - lambda 捕获同理:
[x = std::move(local_obj)]{}才能移动捕获,[=]或[&]都不会触发移动
std::thread 传引用必须用 std::ref
直接写 std::thread t(func, my_var);,哪怕 func 原型是 void func(int&),也会编译失败——std::thread 内部尝试拷贝 my_var,而引用类型不可拷贝。
正确做法只有一种:std::ref 显式包装:
立即学习“C++免费学习笔记(深入)”;
int x = 42;
std::thread t([](int& v) { v *= 2; }, std::ref(x));
t.join();
// x 现在是 84-
std::ref返回的是std::reference_wrapper,它可拷贝、可存储在线程内部,最终还原为左值引用传给函数 - 别用
&x直接传:这会把地址当值传进去,函数收到的是int*,类型不匹配 -
std::cref用于 const 引用场景,原理相同
lambda 捕获参数比直接传参更灵活但要注意生命周期
比起裸函数 + 多个参数,用 lambda 捕获往往更直观,尤其要传多个变量或需要闭包逻辑时。
典型误用:std::thread t([local_vec]{ process(local_vec); }); —— 如果 local_vec 是栈上对象,线程启动前函数已返回,local_vec 被析构,访问就是未定义行为。
- 捕获值(
[v = std::move(local_vec)])安全,但代价是拷贝或移动开销 - 捕获指针(
[ptr = &local_vec])快,但你得确保local_vec的生存期长于线程 - 不要捕获 this 除非你 100% 确认对象不会在线程运行中被销毁;更稳妥的是传
shared_from_this()并在 lambda 里持有std::shared_ptr
std::thread 参数太多或类型复杂时考虑 std::bind
当目标函数参数多、有默认值、或部分参数需绑定固定值时,std::bind 比裸构造 std::thread 更清晰,也避免模板推导失败。
例如:void log(const std::string& tag, int level, const char* msg);,你想固定 tag 和 level,只在线程里传 msg:
auto bound = std::bind(log, "net", 3, std::placeholders::_1); std::thread t(bound, "connection timeout"); t.join();
-
std::bind返回的可调用对象会把未绑定的占位符(如_1)延迟到调用时填充,适合解耦固定参数和动态参数 - 注意:
std::bind同样默认拷贝参数;若要移动,得写成std::bind(func, std::move(x), _1) - C++20 起,推荐优先用 lambda 替代
std::bind(更易读、无隐藏拷贝),但老项目或复杂绑定逻辑中std::bind仍有存在价值
最常被忽略的一点:所有传给 std::thread 的参数,无论怎么包装,最终都必须满足「可移动构造」或「可拷贝构造」;不满足就编译不过,而不是运行时报错。检查报错信息里的“deleted function”提示,基本就能定位是哪个参数没处理好移动语义。










