std::thread()函数的第一个参数的使用细节
今天突然想着写一遍生产者消费者模型,结果写代码时遇到了一连串问题,就把他记录下来供后面参考吧。
在下面的代码中,当我希望在构造函数中通过thread p1(sample::consumer , this , 1);
来创建一个消费者线程时,发现该语句报错Reference to non-static member function must be called
,后面发现这是提示传入的非静态成员函数要能够调用,把这句改成thread p1(&sample::consumer , this , 1);
就不会报错了,因此我觉得非常奇怪,sample::consumer
和&sample::consumer
不是都能够代表函数指针吗,为什么第一种写法会出错呢?
后面查资料知道thread()
函数的第一个参数必须要是可调用对象,而如果传入的是函数指针的话,函数指针是分为普通函数指针(全局函数、静态成员函数等,总而言之就是不需要绑定到某个实例上就可以独立运行的函数)和成员函数(也就是需要与某个实例进行绑定才能运行的函数),而对于thread()
和bind()
来说,如果传入的形式是thread p1(sample::consumer , this , 1);
,那么sample::consumer
实际上会被当做是普通函数指针,进而将后面两个参数作为sample::consumer
函数的参数传入,结果就是报错(因为没有产生一个可调用的对象嘛),而如果传入的形式是thread p1(&sample::consumer , this , 1);
,那么sample::consumer
实际上会被当做是成员函数指针(这个规则就是C++规范定义的),而成员函数指针的第一个参数默认就是指向执行对象的指针,因此整个式子也就能够被顺利生成一个可调用对象。
因此我之前疑问的原因就是因为对C++规范中对普通函数和成员函数的区分不熟悉,如果要把成员函数以及其要绑定的对象指针进行绑定,那么传入的函数名前面一定要加&,以告诉编译器这是一个成员函数。,而如果对于普通的取函数指针,那么函数名前面加不加&,都能得到正确的函数地址。
另外还有个小细节就是thread p1(&sample::consumer , this , 1);
如果改成thread p1(&consumer , this , 1);
,也会报错,这其实还是可以归结为编译器要把普通函数和成员函数区别开,也就是对于成员函数,必须要把它的全名,也就是包括类名这种命名空间也要写全,这主要是为了将普通函数区分开,比如在下面代码中对于静态成员变量hello()
函数,在类的构造函数中就只需要通过thread p2(hello);
来将其传入。
#include <bits/stdc++.h>
using namespace std;
class sample {
private:
int task = 1;
mutex mtx;
condition_variable cv;
queue<int> tasks;
int queue_max = 8;
void prodocutor(int id) {
unique_lock<mutex> lk(mtx);
while(tasks.size() >= queue_max) {
cv.wait(lk,[&] {return tasks.size() < queue_max;});
}
int tmp = task++;
tasks.push(tmp);
lk.unlock();
cv.notify_all(); // 生产者添加任务后唤醒消费者
cout << "prodocutor " << id << "produce task:" << tmp << endl;
this_thread::sleep_for(chrono::seconds(1));
lk.lock();
}
void consumer(int id) {
unique_lock<mutex> lk(mtx);
while(tasks.size() == 0 ) {
cv.wait(lk, [&] {return tasks.size() > 0 ;});
}
int tmp = tasks.front();
tasks.pop();
lk.unlock();
cv.notify_all(); // 消费者消耗任务后唤醒生产者
cout << "consumer " << id << "consume task:" << tmp << endl;
// this_thread::sleep_for(chrono::seconds(1));
lk.lock();
}
static void hello() {
}
public:
sample() {
thread p1(sample::consumer , this , 1); // 错误写法
//thread p1(&sample::consumer , this , 1); // 正确写法
thread p2(hello); // 对于静态成员函数就不需要加命名空间
};
};
int main() {
}
PS:用GPT总结了下*类的成员变量和成员函数的存储位置,简洁来讲就是也就是类当中有成员变量和成员函数,对于静态成员变量都是存储在全局区的,而非静态成员变量根据它是局部变量还是动态申请的内存来决定是在栈区还是在堆区,而函数都是在代码区的。