C++中 线程函数为静态函数 及 类成员函数作为回调函数
线程函数为静态函数:
线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运行,静态函数的地址是不变的,并不在线程堆栈中static只是起了一个装饰的作用,所以二者并没有必然的关系
线程也是一种资源,是受系统调度的。因此,你必须要让系统知道你的线程的起始位置,即线程函数的指针。Window系统的接口(API)是C标准的,系统在启动线程时,需要直接得到线程的起始位置,因此你也必须向系统直接传递这样一个直接的函数指针,这与C++其它的特性是无关的。
因为对于非静态成员函数而言,默认情况下,参数列表中都会有一个this指针,例如fun(自定义参数),实际上编译后就变成这样类型:fun(自定义参数,某个类 * this)。这样编译就会出错,多了一个参数,所以这个函数就不能作为线程函数了。加上static修饰之后,类的成员函数就不会加上默认this了,所以符合调用规定。
在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static !在C语言中,我们使用pthread_create创建线程,线程函数是一个全局函数,所以在C++中,创建线程时,也应该使用一个全局函数。static定义的类的成员函数就是一个全局函数。
类成员函数作为回调函数
回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。
普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。
这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。
这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:
int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );
方法一:回调函数为普通函数,但在函数体内执行成员函数
class MyClass
{
pthread_t TID;
public:
void func()
{
//子线程执行代码
} bool startThread()
{//启动子线程
int ret = pthread_create( &TID , NULL , callback , this );
if( ret != )
return false;
else
return true;
}
}; static void* callback( void* arg )
{//回调函数
((MyClass*)arg)->func();调用成员函数
return NULL;
} int main()
{
MyClass a;
a.startThread();
}
类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void*变为MyClass*,然后再调用arg->func()执行子线程的代码。
这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。
方法二:回调函数为类内静态成员函数,在其内部调用成员函数
在方法一上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码如下:
class MyClass
{
static MyClass* CurMy;//存储回调函数调用的对象
static void* callback(void*);//回调函数
pthread_t TID;
void func()
{
//子线程执行代码
} void setCurMy()
{//设置当前对象为回调函数调用的对象
CurMy = this;
}
public:
bool startThread()
{//启动子线程
setCurMy();
int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );
if( ret != )
return false;
else
return true;
}
};
MyClass* MyClass::CurMy = NULL;
void* MyClass::callback(void*)
{
CurMy->func();
return NULL;
} int main()
{
MyClass a;
a.startThread();
}
类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback当作回调函数,执行CurMy->func()的代码。每次建立线程前先要调用setCurMy()来让CurMy指向当前自己。
这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。
方法三:对成员函数进行强制转换,当作回调函数
class MyClass
{
pthread_t TID;
void func()
{
//子线程执行代码
}
public:
bool startThread()
{//启动子线程
typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,该函数参数为void*,返回值为void*
FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型
int ret = pthread_create( &TID , NULL , callback , this );
if( ret != )
return false;
else
return true;
}
}; int main()
{
MyClass a;
a.startThread();
}
这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。
方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。
C++中 线程函数为静态函数 及 类成员函数作为回调函数的更多相关文章
- static类成员(变量和函数)
0. 使用背景 对于特定类类型的全体对象而言,访问一个全局对象有时是必要的.也许,在程序的任意点需要统计已创建的特定类类型对象的数量:或者,全局对象可能是指向类的错误处理例程的一个指针:或者,它是指向 ...
- Java中变量之局部变量、本类成员变量、父类成员变量的访问方法
变量:局部变量.本类成员变量.父类成员变量 如何访问:如果变量名相同,则采用就近原则,哪个变量离所要调用的访问最近,那就么就输出,优先顺序为:局部变量 > 本类成员变量 > 父类成员变量 ...
- call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
<?php function foobar($arg, $arg2) { echo __FUNCTION__, " got $arg and $arg2\n"; } clas ...
- python 类成员函数
http://cowboy.1988.blog.163.com/blog/static/75105798201091141521583/ 这篇文章总结的非常好 主要注意的地方是 1,在类内调用成员函数 ...
- c++空指针调用类成员函数
最近在看C++动态绑定问题时(理解静态绑定时)发现的问题:能用空指针调用类的成员函数(gcc,vs2013下都可以). 例子: class animal { public: void sleep(){ ...
- 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)
本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上? 成员函数可以被看作是类 ...
- python中进程池和回调函数
一.数据共享 1.进程间的通信应该尽量避免共享数据的方式 2.进程间的数据是独立的,可以借助队列或管道实现通信,二者都是基于消息传递的. 虽然进程间数据独立,但可以用过Manager实现数据共享,事实 ...
- 理解和使用 JavaScript 中的回调函数
理解和使用 JavaScript 中的回调函数 标签: 回调函数指针js 2014-11-25 01:20 11506人阅读 评论(4) 收藏 举报 分类: JavaScript(4) 目录( ...
- C++中回调函数(CallBack)的使用
如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过. 其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递this指针给其成员函数从而 ...
随机推荐
- Maven的下载,安装,配置,测试,初识以及Maven私服
:Maven目录分析 bin:含有mvn运行的脚本 boot:含有plexus-classworlds类加载器框架 conf:含有settings.xml配置文件 lib:含有Maven运行时所需要的 ...
- 关于Spring MVC 中地址栏访问 /WEB-INF下的.jsp
WEB-INF是对资源的保护,直接在地址栏访问WEB-INF目录下的页面,会显示404,关于为什么要把页面放在WEB-INF下,可以自行百度 在这里我是用SpringMVC 对WEB-INF目录下的页 ...
- hihocoder 编程练习赛23
第一题:H国的身份证号码I 题意:一个N位的正整数(首位不能是0).每位数字都小于等于K,并且任意相邻两位数字的乘积也小于等于K.按从小到大的顺序输出所有合法的N位号码,每个号码占一行. 思路:dfs ...
- POJ 1258 Agri-Net (Prim&Kruskal)
题意:FJ想连接光纤在各个农场以便网络普及,现给出一些连接关系(给出邻接矩阵),从中选出部分边,使得整个图连通.求边的最小总花费. 思路:裸的最小生成树,本题为稠密图,Prim算法求最小生成树更优,复 ...
- ThreadLocal、Volatile、synchronized、Atomic
前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...
- python全栈开发day19-面向对象初识
1.昨日内容回顾 模块: 1.什么是模块,什么是包 py文件就是模块,包是包含一系列py文件(__init__.py)的文件夹. 2.模块的导入相当于相当于执行了导入的模块,首次 ...
- 003 将spark源码导入到IDEA中
1.解压源代码 2.进入IDEA的首界面 3.使用open将解压的工程加载 4.将文件的形式改成maven项目 5.使用
- 聊聊Java 8 Lambda 表达式
早在2014年oracle发布了jdk 8,在里面增加了lambda模块.于是java程序员们又多了一种新的编程方式:函数式编程,也就是lambda表达式.我自己用lambda表达式也差不多快4年 ...
- 三篇文章带你极速入门php(一)之语法
本文适合阅读用户 有其他语言基础的童鞋 看完w3cschool语法教程来回顾一下的童鞋(传送门,想全面看一下php语法推荐这里) 毫无基础然而天资聪慧颇有慧根(不要左顾右看说的就是你,老夫这里有一本& ...
- 基于zepto的移动端轻量级日期插件
前言 做过移动Web开发的同学都知道,移动端日期选择是很常见的需求.在PC端,我们有很丰富的选择,比较出名的就有Mobiscroll和jQuery UI Datepicker.个人看来,这些插件存在的 ...