最近在学习C++11的多线程技术,对std::thread构造函数需要传入的第一个参数的类型产生的疑惑,在C++11中增加了可调用对象(Callable Objects)的概念,包括以下几种:
- 函数指针
- 重载operator()运算符的类对象
- lambda表达式
- std::function
std::thread的第一个参数要求的类型就是Callable Objects,让我产生疑惑就是第一种——函数指针,因为在一些书籍里我看到了很多不同的写法。比如:
// 第一种
void hello()
{
printf("thread hello!!!\n");
}
std::thread t1(hello);
// 第二种
class thread_test
{
public:
thread_test() = default;
void run()
{
printf("thread_test running !!!\n");
}
};
thread_test obj_test;
std::thread t2(&thread_test::run, &obj_test);
那普通函数、成员函数、静态成员函数以及在函数名前加"&"到底有什么区别呢?
(以下仅是些个人理解,我不是汇编大神,不能从更本质的角度分析,如有错误请多包涵)
首先是说普通函数、成员函数、静态成员函数三者的区别:
从内存本质上来说,三者其实没太大区别,都位于代码段。成员函数只是比普通函数多了一层类的外壳,调用的时候需要与实际的类关联。普通函数更像是静态成员函数,两者传入参数的方式比较一致,而非静态成员函数的区别就是的第一个参数是默认的this指针(当然不需要显式的调用)。
无论是静态或者是非静态成员函数,本质上都是属于类的,而不是具体对象。从设计模式的角度其实很好理解,因为这些成员函数对于每个实际对象其实都是一样的,而且是在class设计实现阶段就定死不会变化的,所以只需要在内存代码段保留一份即可。而类中对于每个对象可能不同的部分,比如成员变量、虚函数表等,这些可能在程序执行过程中发生动态变化,所以需要和具体对象绑定在一起,有多少个对象,就有多少个成员变量、虚函数表。
总结如下:
成员函数是和类绑定在一起的,有且仅有一份,位于代码段。
成员变量、虚函数表等不同对象可能不同值的部分,和具体所属的对象绑定在一起,位于栈上或堆上(new)。
再者聊聊普通函数、成员函数、静态成员函数、函数名前加"&"以及函数指针的关系
函数名可以直接赋值给函数指针,这类似于数组名可以直接赋值给指针的用法。函数名和数组名从本质上来说都不是指针类型,执行赋值操作实际是编译器帮我们做了隐性的转换,它会默认帮我们在函数名或者数组名前加上取地址符"&"。比如上面的例子,函数名hello和&hello从数值上来说是一样的,其实这是很匪夷所思的,函数指针不应该函数hello存放的地址吗,怎么加不加"&"一样呢?从个人角度来说,加上取地址符更符合我的理解。很多时候,越简练的程序理解起来就越困难,这就是代价吧。
成员函数和静态成员函数也类似,给函数指针赋值或者当做函数指针参数传入时,函数名前加不加"&"都可以。用一个实际的程序例证下。
#include <iostream>
void hello()
{
printf("thread hello!!!\n");
}
class test
{
public:
test() = default;
void run()
{
printf("test running !!!\n");
}
static void static_run()
{
printf("test static running !!!\n");
}
};
int main()
{
printf("hello = 0x%x\n", hello);
printf("&hello = 0x%x\n\n", &hello);
printf("test::run = 0x%x\n", test::run);
printf("&test::run = 0x%x\n\n", &test::run);
printf("test::static_run = 0x%x\n", test::static_run);
printf("&test::static_run = 0x%x\n\n", &test::static_run);
getchar();
return 0;
}
实际运行结果:
hello = 0x401080
&hello = 0x401080
test::run = 0xffffcba0
&test::run = 0xffffcba0
test::static_run = 0x401840
&test::static_run = 0x401840
可以看到,普通函数、静态成员函数、非静态成员函数,函数名与函数名前加"&"的值本质上是一样的。
创建thread线程时,函数名前需要加取地址符吗?
答案是加不加都可以。理由同上,C++编译器会自动为我们添加取地址符,特殊的是非静态成员函数至少需要一个参数(this指针),所以创建线程时必须得有一个实际的对象为我们提供这个this地址,否则编译是通不过的。Talk is cheap, show me the code 上代码验证。
#include <iostream>
#include <thread>
void hello()
{
printf("thread hello!!!\n");
}
class thread_test
{
public:
thread_test() = default;
void run()
{
printf("class thread_test running !!!\n");
}
static void static_run()
{
printf("class thread_test static running !!!\n");
}
};
int main()
{
thread_test obj_test;
std::thread t1(hello);
std::thread t2(&hello);
t1.join();
t2.join();
std::cout << std::endl;
std::thread t3(thread_test::static_run);
std::thread t4(&thread_test::static_run);
t3.join();
t4.join();
std::cout << std::endl;
std::thread t5(thread_test::run, &obj_test);
std::thread t6(&thread_test::run, &obj_test);
t5.join();
t6.join();
getchar();
return 0;
}
实际运行结果:
thread hello!!!
thread hello!!!
class thread_test static running !!!
class thread_test static running !!!
class thread_test running !!!
class thread_test running !!!
可以看到普通函数、静态成员函数、非静态成员函数三者函数名前,无论加不加"&"都可以作为std::thread的函数指针参数正常运行。