std::thread构造时参数被拷贝或移动到线程内部,安全可靠;普通函数按值传参,引用需std::ref;lambda推荐按值捕获;成员函数首参为对象指针或引用;避免悬垂引用。

std::thread 构造时直接传参(最常用)
创建线程时,可将函数对象和参数一并传给 std::thread 构造函数。参数会被**拷贝(或移动)进线程内部存储**,与主线程栈无关,安全可靠。
- 普通函数:参数按值传递,支持 const 引用、右值引用等,但注意引用类型需显式用 std::ref 或 std::cref
- lambda 表达式:捕获方式决定参数生命周期;推荐按值捕获(
[=])或显式移动捕获([x = std::move(x)]),避免悬垂引用 - 成员函数:第一个参数必须是对象指针或引用(如
&obj或std::ref(obj)),后续才是成员函数的实参
示例:
立即学习“C++免费学习笔记(深入)”;
void foo(int x, const std::string& s) { /* ... */ }
class A { public: void bar(double d) {} };
int main() {
std::string s = "hello";
A a;
// 按值传参(s 被拷贝)
std::thread t1(foo, 42, s);
// 传引用需 std::ref
std::thread t2(foo, 42, std::ref(s));
// 调用成员函数
std::thread t3(&A::bar, &a, 3.14);
t1.join(); t2.join(); t3.join();
}
通过 lambda 捕获外部变量(灵活且直观)
lambda 是封装参数最自然的方式,尤其适合需要访问局部变量、或参数类型复杂/不可拷贝的场景。
- 按值捕获
[=]:所有自动变量被拷贝进 lambda 闭包,线程内安全使用 - 按引用捕获
[&]:危险!若线程生命周期超过变量作用域,会引发未定义行为 - 混合捕获(C++14+):如
[x = std::move(x), &y],精确控制每个变量的传递方式
示例:
立即学习“C++免费学习笔记(深入)”;
std::vectordata = {1,2,3}; // 安全:data 被拷贝进闭包 std::thread t([data]{ for (int x : data) std::cout << x << " "; }); t.join(); // 危险!data 可能已析构 // std::thread t2([&data]{ ... }); // ❌ 不要这样写
使用 std::bind 预绑定参数(较少用,但兼容旧代码)
std::bind 可提前绑定部分或全部参数,生成可调用对象,再传给 std::thread。本质是函数对象适配,语义清晰但略显冗余。
- 绑定的参数同样被拷贝(或移动),线程安全
- 占位符
_1, _2...用于预留运行时传入的参数 - 现代 C++ 更推荐 lambda,bind 主要用于需要延迟绑定或适配器组合的场合
示例:
立即学习“C++免费学习笔记(深入)”;
void log(const char* tag, int val, const std::string& msg) {
std::cout << tag << ": " << val << " - " << msg << "\n";
}
std::string msg = "done";
// 绑定前两个参数,第三个留空
auto bound = std::bind(log, "INFO", 100, std::placeholders::_1);
std::thread t(bound, msg); // 运行时传 msg
t.join();
传参常见陷阱与注意事项
参数传递看着简单,但几个关键点容易出错:
-
引用参数默认不生效:直接写
std::thread(f, std::ref(x)),否则传的是 x 的拷贝 - 避免裸指针/引用指向栈变量:线程中使用局部变量地址是典型悬垂指针,必崩溃
-
移动语义要显式 std::move:若想转移资源(如 unique_ptr),必须用
std::move(ptr),否则编译失败 -
可变参数模板完美转发可行但复杂:自定义线程包装器可用
std::forward,日常开发无需手动实现(args)...
一句话总结:优先用构造函数传值 + std::ref 处理引用,lambda 捕获按需选择 [=],避开栈变量引用,就基本不会踩坑。











