· 在 C++ 多线程编程中,很多初学者都会遇到一个问题:
成员函数怎么在线程中调用?对象要传指针还是引用?能不能直接传值?
本文通过一个小例子,带你从原理出发,彻底搞懂 std::thread
调用类成员函数时,传参的几种方式和区别。
示例类 A
我们先定义一个简单的类 A
,包含一个成员变量和一个成员函数:
cpp
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int x = 0;
void foo() {
x = 100;
cout << "foo() called, x = " << x << endl;
}
};
错误用法一:直接传对象
cpp
A a;
std::thread t(&A::foo, a); // ⚠️ 虽然能编译,但有坑
t.join();
解释:
-
std::thread
默认是 值传递; -
你传的是
a
,但实际上线程中收到的是 a 的副本; -
所以你在线程里修改的是副本,不会影响主线程中的
a
。
示例输出:
cpp
cout << a.x << endl; // 输出 0,不是 100
你以为改了,其实没改!
正确方式一:传指针
cpp
A a;
std::thread t(&A::foo, &a); // 传对象地址
t.join();
原理:
-
&A::foo
是成员函数指针; -
&a
是对象地址(A*
); -
所以最终调用的就是
(a.*foo)()
,真正作用于原对象。
使用智能指针也可以:
cpp
auto a = std::make_shared<A>();
std::thread t(&A::foo, a); // ✅ shared_ptr 也能用,自动转换为 A*
正确方式二:传引用(std::ref)
传引用而不是指针,标准库也支持
cpp
A a;
std::thread t(&A::foo, std::ref(a)); // ✅ 强制引用传递
t.join();
错误用法二:对象不可拷贝
如果你的类禁止了拷贝构造:
cpp
class A {
public:
A() = default;
A(const A&) = delete; // ❌ 禁止拷贝
void foo() {}
};
那么这行:
std::thread t(&A::foo, a); // ❌ 编译失败
会报错!因为 a
不能被拷贝进线程。
总结:传对象给线程的三种方式对比
写法 | 是否作用于原对象 | 是否安全 | 推荐情况 |
---|---|---|---|
std::thread t(&A::foo, a); |
❌ 否(拷贝副本) | ✅ | 对象可拷贝、修改无所谓 |
std::thread t(&A::foo, &a); |
✅ 是 | ✅ | 推荐,简单直接 |
std::thread t(&A::foo, std::ref(a)); |
✅ 是 | ✅ | 推荐,传引用更语义清晰 |
std::thread t(&A::foo, smartPtr); |
✅ 是 | ✅ | 推荐用于共享对象管理 |
补充:C++ 成员函数调用的本质到底是什么?为什么要传对象?
平常写代码这样调用成员函数:
A a;
a.foo();
这句话到底发生了什么呢?它为什么能运行?
foo
是属于类 A
的函数,但是它不能单独运行,必须靠"某个具体的对象"来调用。
就好比:
-
foo
是"技能"; -
a
是"拥有这个技能的人";
只有 a
这个人,才可以施展 foo
这个技能。
语言层面怎么实现的?
C++ 编译器做了一件事:
它把成员函数 转换成带有隐藏参数的普通函数。
这个隐藏参数叫 this
,代表"哪个对象调用了这个函数"。
所以 a.foo()
,其实等价于:
foo(&a);
这里的 &a
就是 this
指针,告诉函数"你是代表谁去执行的"。
这样解释一下:
-
foo
本质就是:void foo(A* this)
{
// 函数体
} -
a.foo()
实际调用是:foo(&a); // 给函数传入a的地址
那多线程调用成员函数为什么要传对象?
当你写:
std::thread t(&A::foo, a);
你是在告诉线程:
-
我要调用类
A
的成员函数foo
, -
用
a
这个对象来调用它(传入this
指针)
线程内部实际上是:
(a.*&A::foo)();
也就是用 a
这个对象执行 foo
。
如果你写成:
std::thread t(&A::foo);
这样不行!线程里没告诉它用谁调用函数,它不知道 this
是谁!
总结几个关键点:
你写的代码 | 编译器理解成什么 | 为什么要传对象 |
---|---|---|
a.foo() |
foo(&a) |
需要 this 指针,告诉函数"你代表谁执行" |
std::thread t(&A::foo, a); |
线程内部调用 (a.*&A::foo)() |
线程里也需要 this 指针(对象),才能调用成员函数 |
std::thread t(&A::foo); |
编译错误,因为没传 this 指针 |
缺少对象,没法调用成员函数 |
打个简单比喻
假如成员函数是电话指令 ,对象是电话机。
-
你要给电话机发送指令(成员函数调用),必须先知道是哪部电话机(对象);
-
你不能直接说"拨号",却不给电话机(对象);
-
this
就是电话机的地址; -
线程调用成员函数,也必须知道是哪部电话机,才能执行指令。
所以,为什么 std::thread
调用成员函数时:
-
第一个参数是成员函数指针("电话指令");
-
第二个参数必须是对象或者对象指针("电话机");
额外说明
-
静态成员函数 没有
this
,它更像普通函数,不需要对象,线程里可以直接调用; -
传对象时是传"指针"或"引用",因为成员函数需要用
this
指向那个对象。