关于C++ 回调函数理解
编程中肯定会遇到在C++中使用回调函数的情况。
但是为什么要使用回调函数呢?
程序模块之间的调用大致分为三种:
同步调用是阻塞式的,回调模式是一种双向的调用,异步调用可以解决同步阻塞问题。
回调是异步的基础,因此回调不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。
我们需要理解回调函数设计原理。
- 回调函数可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。
- 回调可用于通知机制,即简单的一对一的观察者模式。
例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。
但是,其实回调函数是继续自C语言的,因为回调函数的原理,无非就是传入一个函数地址,然后在达到某种触发条件的时候,可以通过这个函数地址调用用户(此用户指回调函数的使用者)希望的函数接口,继而传递参数。
所以回调函数的实现是比较简单的,只需要将函数地址保存,然后触发调用即可。
那我们就说明一下在C++中回调函数的使用。
在C++中,如何使用类的成员函数作为回调函数入参呢?
我们先理解一下C++静态成员函数。
在C++中普通成员函数和静态成员函数的区分如下:
- 静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存。举例如下:
1 2 3 4 5 6 7
| class base{ static int func1(); int func2(); }
int (*pf1)() = &base::func1; int (base::*pf2)() = &base::func2;
|
- 静态成员函数不可以调用类的非静态成员。静态成员函数不含this指针,而普通成员函数默认携带this指针参数。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <stdio.h> #include <iostream> using namespace std; class MyClass { public: static int FunA(int a, int b) { cout << "call FunA" << endl; return a + b; } void FunB() { cout << "call FunB" << endl; } void FunC() { cout << "call FunC" << endl; } int pFun1(int (*p)(int, int), int a, int b) { return (*p)(a, b); } void pFun2(void (MyClass::*nonstatic)()) { (this->*nonstatic)(); } }; int main() { MyClass* obj = new MyClass;
int (*pFunA)(int, int) = &MyClass::FunA; cout << pFunA(1, 2) << endl; void (MyClass::*pFunB)() = &MyClass::FunB; (obj->*pFunB)(); obj->pFun1(&MyClass::FunA, 1, 2); obj->pFun2(&MyClass::FunB); obj->pFun2(&MyClass::FunC); delete obj; return 0; }
|
有了以上区分,我们可以知道静态成员函数作为回调函数的入参是有先天优势的。
非静态成员函数不能直接作为回调函数的原因:
为了实现回调,我们必须把this指针给转换掉!可为了在该函数中可以直接操作该类中的成员,我们必须保留this指针!所以这是矛盾的。
如果在类封装回调函数:
- 回调函数只能是全局的或是静态的。
- 全局函数会破坏类的封装性,故不予采用。
- 静态函数只能访问类的静态成员,不能访问类中非静态成员
但是我们依旧可以做些变化,可以实现静态成员函数和非静态成员函数均作为回调函数入参。
下面是C++常用的三种回调函数的例子:
1. 非静态成员函数作为回调函数参数例子
非静态成员函数作为回调函数参数的天生弊端已经在上面对比中讲清楚,所以我们回调函数的设计必须要能够满足非静态成员函数的情况。
- 设计普通成员函数作为回调函数:(我们采用C方式实现回调函数,和C++并没有什么区别)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| typedef struct { int aa; int bb; }testMsgType;
typedef int (*ptestCB)( testMsgType *, void *); ptestCB m_test; void* m_kk;
int setCBTest(ptestCB pf, void * kk) { printf("wei....... set cb ok!!!\n"); m_test = pf; m_kk = kk; return 0; }
int cbfuc() { printf("cb func !!!\n"); stZigBeeMsg* type = new testMsgType; m_test(type,m_kk); return 0; }
|
1 2 3 4 5 6 7 8 9 10
| class Caa { public: Caa(); ~Caa(); init(); int onmycb(stZigBeeMsg * p,void* kk); int dealCB(ptestCB* p); }
|
* Caa.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Caa::Caa() { init(); }
Caa::~Caa() { } bool Caa::init() { setCBTest((ptestCB)&Caa::onmyevent,this); return true; }
int Caa::onmyevent(testMsgType * p,void* aa) { ((CEventMgmt*)aa)->dealCB(p); return 0; }
int Caa::dealCB(testMsgType* p) { return 0; }
|
2. 静态成员函数作为回调函数参数,非静态类方式
静态成员函数地址作为回调函数参数时,因为静态成员函数没有this指针,所以回调函数的设计比较简单,不用包含void *来传递保存this指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| typedef int (*ptestCB)( stZigBeeMsg *);
ptestCB m_test;
int setCBTest(ptestCB pf) { printf(" set cb ok!!!\n"); m_test = pf; return 0; }
int cbfuc() { printf("cb func !!!\n"); testMsgType* type = new testMsgType; m_test(type); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Caa { public: Caa(); ~Caa(); init(); private: void setCurClass() { spCB = this; } static int onmycb(stZigBeeMsg * p); int dealCB(ptestCB* p); private: static Caa* spCB; }
|
* Caa.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Caa* Caa::spCB = NULL; Caa::Caa() { init(); }
Caa::~Caa() { } bool Caa::init() { setCurClass(); setCBTest(&Caa::onmyevent); return true; }
int Caa::onmyevent(testMsgType * p) { spCB->dealCB(p); return 0; }
int Caa::dealCB(testMsgType* p) { return 0; }
|
3. 静态成员函数作为回调函数参数进阶,采用单例设计模式
和上面的例子思路是一致的,上面采用一个类的静态对象来做中间转换,继而调用非静态成员函数。
那么假如我设计类为单例模式,则直接可以在静态成员函数回调内部进行调用非静态成员函数,如下:
1 2 3 4 5 6 7 8 9
| Caa * Caa::instance() { static Caa *p; if (p == NULL) { p = new Caa; } return p; }
|
1 2 3 4 5 6
| int Caa::onmyevent(testMsgType * p) { Caa::instance()->dealCB(p); return 0; }
|
这样即可达到例子2中的效果。
其实在C++中使用回调函数,只有明白几个基础知识点即可:
- 回调函数设计原理
- 静态成员函数和非静态成员函数区别
- 静态成员函数指针、类成员函数指针、this指针