注:与tr1::function对象结合使用,能获得更好的效果,详情见http://blog.csdn.net/this_capslock/article/details/38564719

回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。

普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。

这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。

这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:

  1. int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址,即回调函数。
最后一个参数是运行函数的参数。
 
这里我们只关注第三个参数start_run,它是一个函数指针,指向一个以void*为参数,返回值为void*的函数,这个函数被当作线程的回调函数使用,线程启动后便会执行该函数的代码。
 
方法一:回调函数为普通函数,但在函数体内执行成员函数
见以下代码:
  1. class MyClass
  2. {
  3. pthread_t TID;
  4. public:
  5. void func()
  6. {
  7. //子线程执行代码
  8. }
  9. bool startThread()
  10. {//启动子线程
  11. int ret = pthread_create( &TID , NULL , callback , this );
  12. if( ret != 0 )
  13. return false;
  14. else
  15. return true;
  16. }
  17. };
  18. static void* callback( void* arg )
  19. {//回调函数
  20. ((MyClass*)arg)->func();调用成员函数
  21. return NULL;
  22. }
  23. int main()
  24. {
  25. MyClass a;
  26. a.startThread();
  27. }

类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void*变为MyClass*,然后再调用arg->func()执行子线程的代码。

这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。

方法二:回调函数为类内静态成员函数,在其内部调用成员函数

在方法一上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码如下:

  1. class MyClass
  2. {
  3. static MyClass* CurMy;//存储回调函数调用的对象
  4. static void* callback(void*);//回调函数
  5. pthread_t TID;
  6. void func()
  7. {
  8. //子线程执行代码
  9. }
  10. void setCurMy()
  11. {//设置当前对象为回调函数调用的对象
  12. CurMy = this;
  13. }
  14. public:
  15. bool startThread()
  16. {//启动子线程
  17. setCurMy();
  18. int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );
  19. if( ret != 0 )
  20. return false;
  21. else
  22. return true;
  23. }
  24. };
  25. MyClass* MyClass::CurMy = NULL;
  26. void* MyClass::callback(void*)
  27. {
  28. CurMy->func();
  29. return NULL;
  30. }
  31. int main()
  32. {
  33. MyClass a;
  34. a.startThread();
  35. }

类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback当作回调函数,执行CurMy->func()的代码。每次建立线程前先要调用setCurMy()来让CurMy指向当前自己。

这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。

方法三:对成员函数进行强制转换,当作回调函数

代码如下:

  1. class MyClass
  2. {
  3. pthread_t TID;
  4. void func()
  5. {
  6. //子线程执行代码
  7. }
  8. public:
  9. bool startThread()
  10. {//启动子线程
  11. typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,该函数参数为void*,返回值为void*
  12. FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型
  13. int ret = pthread_create( &TID , NULL , callback , this );
  14. if( ret != 0 )
  15. return false;
  16. else
  17. return true;
  18. }
  19. };
  20. int main()
  21. {
  22. MyClass a;
  23. a.startThread();
  24. }

这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。

方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。

暂时就列出这些方法,以后发现更好的再来补充,over

http://blog.csdn.net/this_capslock/article/details/17001003

---------------------------------------------------------------------------------------

4楼 eziowayne 2015-04-17 15:38发表 [回复]
个人推荐第一种方法,第二种方法适用面窄,第三种是错误的方法,千万不能这么用。
3楼 __JacHan 2014-04-28 14:44发表 [回复]
用的时候用楼主的方法三实在调不通,论坛里面有人这样解释的:为了实现回调,我们必须把this指针给转换掉!可为了在该函数中可以直接操作该类中的成员,我们必须保留this指针!所以这是矛盾的,通过转换指针的手段是不能达到目的的!
来自:http://bbs.csdn.net/topics/360051927
还是用tr1::function/bind实现了

C++中类成员函数作为回调函数的更多相关文章

  1. C++中 线程函数为静态函数 及 类成员函数作为回调函数

    线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运行,静态函数的地址是不变的,并不在线程堆栈中static只 ...

  2. php变量函数,回调函数

    一,变量可以直接传递函数 <?php function demo($num , $n )//$n是个函数 { for($i=0;$i<$num;++$i) { if($n($i)) { e ...

  3. 【知识点】inline函数、回调函数、普通函数

    目录 一.inline内联函数 1.1 使用 1.2 编译器对 inline 函数处理步骤 1.3 优缺点 1.3.1 优点 1.3.2 慎用内联 1.3.3 不宜使用内联 1.4 虚函数(virtu ...

  4. Go基础系列:函数(2)——回调函数和闭包

    回调函数和闭包 当函数具备以下两种特性的时候,就可以称之为高阶函数(high order functions): 函数可以作为另一个函数的参数(典型用法是回调函数) 函数可以返回另一个函数,即让另一个 ...

  5. php自定义函数之回调函数

    回调函数,可以配合匿名函数和变量函数实现更加优美.复杂的一种函数结构.大理石平台价格 回调函数,就是在处理一个功能的时候,我让让这个功能自定义能力再强一些,我准许调用这个函数的时候,还可以传入一个函数 ...

  6. C++中的Thunk技术 / 非静态类成员函数作为回调函数 的实现方法

    原文:https://blog.twofei.com/616/ 用我的理解通俗地解释一下什么是C++中的Thunk技术吧! Thunk技术就是申请一段可执行的内存, 并通过手动构造CPU指令的形式来生 ...

  7. 通过修改i8042prt端口驱动中类驱动Kbdclass的回调函数地址,达到过滤键盘操作的例子

    同样也是寒江独钓的例子,但只给了思路,现贴出实现代码 原理是通过改变端口驱动中本该调用类驱动回调函数的地方下手 //替换分发函数 来实现过滤 #include <wdm.h> #inclu ...

  8. C语言笔记 08_函数指针&回调函数&字符串&结构体&位域

    函数指针 函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调用函数.传递参数. 函数指针变量的声明: / ...

  9. typedef void(*Fun) (void)是什么意思 函数指针(回调函数) 和函数对象总结

    https://blog.csdn.net/FreeApe/article/details/49124043 bool (*pf)(const string &,const string &a ...

随机推荐

  1. Python核心编程读笔 12:OOP

    第13章 面向对象编程 一.基本概念 1.object类是所有类的基类,如果你的类没有继承任何其他父类,object 将作为默认的父类. 2.python创建实例时无需new: myFirstObje ...

  2. 内存管理——Cocos2d-x学习历程(五)

    Cocos2d-x采用了引用计数与自动回收的内存管理机制. 1.每个对象包含一个用来控制生命周期的引用计数器,它就是CCObject的成员变量m_u- Reference.我们可以通过retainCo ...

  3. C/C++中的far和near两个指针

    Dos 的设计是基于16位的CPU的,也就是CPU中的每个寄存器(Register)只有16位,只能存放0-65535(64K)的值.为了能访问大于64K的内存,人们用了分段的方法,用两个16位的数来 ...

  4. php 接收curl json 数据

    curl -H "Content-Type: application/json" http://127.0.0.1:8000 -X POST -d 'xxxx' php $strP ...

  5. hadoop笔记之MapReduce的运行流程

    MapReduce的运行流程 MapReduce的运行流程 基本概念: Job&Task:要完成一个作业(Job),就要分成很多个Task,Task又分为MapTask和ReduceTask ...

  6. 诞生于饭桌上的jcSQL语言

    相信每个Coder都有心在自己求学阶段可以写一门自己的语言,无论是毕业设计,还是课余爱好:不管是为了提升B格,还是想练手,抑或对其他语言不满,想自己撸一个,只要坚持下去了,都是不错的理由. 现在正值暑 ...

  7. Python调用C/C++的种种方法

    Python调用C/C++的种种方法 2010-12-07 09:59 28433人阅读 评论(1) 收藏 Python是解释性语言, 底层就是用c实现的, 所以用python调用C是很容易的, 下面 ...

  8. 如何在 静态编译的QT 5.5.1 中 使用数据库插件连接 ODBC(调用静态插件)

    前段时间由于工作的关系,需要编写一个将数据插入到 Sql server 2012 的桌面软件. 由于之前使用的是MFC,偶然间接触到了Qt后,被它的简洁惊艳到了,于是便毅然而然的转投到了Qt的怀抱,哈 ...

  9. Android 天天爱消除辅助

    简介 <天天爱消除>是一款移植于手游的消除类益智游戏,该游戏只有通过手机登录QQ跟微信才能进行,这样一来这款游戏必然会大红大紫. 功能 开发Android自动化触屏事件,录制操作脚本,实现 ...

  10. rsyslog 定义模板

    rsyslog默认会将特殊字符(\t)转换成#009 由全局配置$EscapeControlCharactersOnReceive 决定,如果自己需要根据\t处理输出时,需将该选项改为 off. $E ...