传递参数
传递参数
在 C++ 中,创建线程时可以向函数传递参数。std::thread 构造函数接受函数指针或可调用对象作为第一个参数,后续参数会传递给该函数(或可调用对象)。
std::thread 的参数传递机制
- 拷贝(或移动)所有传入的参数;
- 然后,在新线程启动时,把拷贝(或移动)后的对象当作实参传给目标函数。
这意味着:
std::thread t(f, arg1, arg2);
等价于:
auto copied_arg1 = arg1; // 拷贝/移动
auto copied_arg2 = arg2;
launch_thread([=] { f(copied_arg1, copied_arg2); });
值传递:自动拷贝
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
"hello"是字符串字面值(const char*),但f需要std::string。- 线程构造时,自动将其转换为
std::string,并拷贝到新线程内存空间。- 传给
std::thread构造函数的参数是“值传递”(拷贝或移动)进去的。 - 线程函数中的引用参数,是对
std::thread内部拷贝的参数的引用。 - 不是直接引用调用处的变量,而是引用了线程内部拷贝的那个副本。
- 传给
- 所有参数都会被拷贝进新线程。
悬空指针风险:局部变量传递
void f(int i, std::string const& s);
void oops(int some_param) {
char buffer[1024]; // 局部变量
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, buffer); // 传入局部变量
t.detach(); // 主线程提前结束,buffer 可能已销毁
}
buffer是局部变量,生命周期受限于oops函数。std::thread创建新线程时并不能保证立刻执行。- 若线程延迟执行,可能访问已销毁的
buffer,引发 未定义行为。
正确写法:先转换为 std::string
void not_oops(int some_param) {
char buffer[1024];
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, std::string(buffer)); // 显式构造 string
t.detach();
}
整个过程等价于这样:
std::string temp = std::string(buffer); // 构造一个字符串副本
std::thread t(f, 3, temp); // 把 temp 拷贝进 thread 的上下文
线程函数里接收的是 const std::string&,引用的是线程内部保存的 temp 副本。
引用传递:需要 std::ref
// 函数声明:第二个参数是非常量左值引用,必须传入一个真正的变量(不能是临时值)
// 该函数会在线程中尝试修改 data 的内容
void update_data_for_widget(widget_id w, widget_data& data);
void oops_again(widget_id w) {
// 创建一个本地 widget_data 对象,用于在主线程和子线程之间共享
widget_data data;
// 尝试创建线程,目标函数是 update_data_for_widget,传入 w 和 data
// 错误点:
// std::thread 的构造函数会拷贝(或移动)所有传入的参数
// 所以它试图将 data 拷贝到线程的内部上下文中,然后再传给函数
// 但函数参数要求是 widget_data&(非常量左值引用):
// —— 无法用临时值(右值)绑定到非常量左值引用,会导致编译错误
std::thread t(update_data_for_widget, w, data); // ❌ 编译失败
t.join(); // 等待线程结束
// 处理 data(如果上面的线程能修改成功,这里将看到修改结果)
// 但由于传参失败,这里不会发生预期中的修改
process_widget_data(data);
}
update_data_for_widget期望引用,但传入的是一个拷贝。- 第二个参数是非常量左值引用,函数只接受「左值变量」。而
std::thread构造时:- 它把
data拷贝成一个临时对象; - 然后尝试将这个临时对象(右值)绑定给
widget_data& data; - 非法!因为非常量左值引用不能绑定到右值。
- 它把
- 编译器会报错(不能用右值初始化非常量引用)。
正确写法:使用 std::ref
std::thread t(update_data_for_widget, w, std::ref(data));
- 使用
std::ref(data)告诉线程构造函数按引用传递。
调用类的成员函数
无参成员函数
class X {
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work, &my_x); // 等效于 my_x.do_lengthy_work()
带参数成员函数
class X {
public:
void do_lengthy_work(int val);
};
X my_x;
int num = 42;
std::thread t(&X::do_lengthy_work, &my_x, num); // 等效于 my_x.do_lengthy_work(42)
- 第一个参数是成员函数指针,第二个参数是对象指针,之后是参数列表。
只支持移动的对象:用 std::move
有些对象(如 std::unique_ptr)不可拷贝,只能移动:
// 声明一个函数,接收一个 std::unique_ptr 拥有的 big_object
// 注意:参数是按值(通过移动语义)传入的,不是引用
void process_big_object(std::unique_ptr<big_object>);
// 创建一个 std::unique_ptr,指向动态分配的 big_object 对象
std::unique_ptr<big_object> p(new big_object);
// 调用成员函数,准备数据
p->prepare_data(42);
// 使用 std::move 将 p 的所有权转移给线程
// 注意:std::thread 会拷贝/移动所有参数到它的内部(即线程上下文)
// 所以必须 std::move(p),否则不能把 unique_ptr 传进去(它不可拷贝)
// 线程内部再将该 unique_ptr 移交给 process_big_object 函数
std::thread t(process_big_object, std::move(p));
std::move(p)将p所有权转移到线程中。- 原对象
p在主线程中会变成空指针。
关于 std::thread 自身的移动语义
-
std::thread本身是 可移动但不可拷贝 的对象。std::thread t1(func); // std::thread t2 = t1; // ❌ 编译错误!不能复制线程对象- 一个
std::thread实例代表一个具体的线程执行权(ownership)。 如果允许复制,那两个对象都指向同一个线程,谁负责回收资源?会出混乱! - 虽然不能复制,但可以用
std::move()转移一个线程对象的所有权:
std::thread t1(func); // t1 拥有线程 std::thread t2 = std::move(t1); // t2 拥有线程,t1 被置空 - 一个
-
每个线程对象只能拥有一个线程的执行权。
-
可以使用
std::move将std::thread实例的所有权转交给其他线程变量。
参数传递方式对比
| 场景 | 默认行为 | 正确写法 | 风险或注意事项 | 示例代码片段 |
|---|---|---|---|---|
| 普通值类型 | 拷贝 | 直接传入 | 无 | std::thread t(f, 42); |
| 字符串字面值 | 拷贝指针 | std::string("hello") |
指针本身是静态安全,但指向局部数组就悬空 | std::thread t(f, std::string("hello")); |
| 局部变量指针 | 拷贝指针 | 提前构造为 std::string |
函数返回后局部数据销毁,指针悬空 | char buf[100]; std::thread t(f, std::string(buf)); |
| 非 const 引用参数 | 拷贝 | 使用 std::ref(...) |
默认拷贝而非引用,修改无效 | std::thread t(f, std::ref(data)); |
| 成员函数调用 | 函数 + 对象指针 | 成员函数 + 对象指针 + 参数顺序 | 语法不同于普通函数指针调用 | std::thread t(&X::func, &x, arg1); |
| 只可移动对象 | 编译失败 | 使用 std::move(...) |
所有权转移,源对象被置空 | std::thread t(f, std::move(p)); |

浙公网安备 33010602011771号