您的位置:首页 > 编程语言 > C语言/C++

C++中的回调函数——指向类成员的指针

2014-03-12 14:44 99 查看
C++中的回调函数
——指向类成员的指针

在C中我们能够很容易地实现一个指向函数的指针,因此能够方便地实现函数的回调机制。但是在C++中很多人认为类的成员函数不能作为回调函数,因此很多C程序不能移植到C++上来。其实不是这样的,在C++中我们同样可以获取类的成员函数的指针,也能方便地实现函数回调机制。

下面先说一下C/C++中普通函数作为回调函数的情况,然后再说C++ 中类成员函数做为回调函数的情况。

先说一下自己对于回调函数的理解:回调函数通常是指通过函数指针进行调用的函数。例如,我有一个函数指针pFunc,我可以在运行时将不同的函数的地址给pFunc,同时可以通过pFunc来调用它所指向的函数,此时被pFunc指向的函数就称为是一个回调函数。

那么什么是函数指针:首先函数指针是一个指针,只是他不是指向一个变量而是指向一个函数的地址,也就是指向一个函数的入口地址,我们也可以修改该指针,指向不同的函数。函数指针的一般形式如下:

返回值类型 (*函数指针变量名) (参数列表) ;
通常为了方便,我可以对其进行类型重定义:

typedef 返回值类型 (*FUNCPTR) (参数列表) ;

这样,FUCNPTR就是一个函数指针的类型,我们可以直接通过它进行定义函数指针,如:

#include <iostream>
using namespace std ;

int increse(int &value)
{
++ value ;
}

int main (int argc, char **argv)
{
//重定义函数指针类型
typedef int (*FUNCPTR)(int&) ;

FUNCPTR pFunc1 = &increse ; //通过类型重定义的方式声明一个指针
int (*pFunc2)(int&) = &increse ; //直接申明一个指针

int value = 0 ;

pFunc1(value) ;//通过函数指针调用
cout << value << endl;
pFunc2(value) ;//通关函数指针调用
cout << value << endl;

if (pFunc1 == pFunc2)
cout << "pFunc1 and pFunc2 point to the same function" << endl;
else
cout << "pFunc1 and pFunc2 point different functions" << endl;

return  0 ;
}
执行的结果:

1
2
pFunc1 and pFunc2 point to the same function

在C/C++中普通函数最为回调函数很简单了,下面给出一个简单的例子,一目明了:

#include <iostream>
using namespace std ;

typedef int (*FUNCPTR)(int&) ;
enum OPTION {INCRESS, DECRESS, OPTION_NUM} ;
FUNCPTR operations[OPTION_NUM] ;

int increse(int &value)
{
++ value ;
}

int decress(int &value)
{
-- value ;
}

int& numOperation(int &value, OPTION op)
{

if (op >= 0 && op < OPTION_NUM && operations[op])
operations[op](value) ; //执行回调函数

return  value ;

}
int main (int argc, char **argv)
{
operations[INCRESS] = &increse ;
operations[DECRESS] = &decress ;

int value = 0 ;
cout << numOperation(value, INCRESS) << endl ;
cout << numOperation(value, INCRESS) << endl ;
cout << numOperation(value, DECRESS) << endl ;
cout << numOperation(value, DECRESS) << endl ;
return 0 ;
}


在这个例子中,increse()和decress()函数的指针都被存储在operations数组中,作为numOperation()函数的回调函数。用户调用numOperation()函数时,通过改变传入参数来指定要完成的操作,numOperation()函数会根据第二个参数调用不同的回调函数,完成操作。例如,如果用户传入INCRESS,就会调用operations[INCRESS]指针所指向的函数,也就是对value的数值进行加以,如果传入DECRESS,就会调用operations[DECRESS]指针所指的函数。

下面说一下C++中类成员函数作为回调函数的情况,首先我们要得到类成员函数的指针。
C++类的成员(这里主要说成员函数)分为静态成员和非静态成员,对于静态成员由于它不是任何对象实例的组成部分,所以不需要特殊的语法来指向static的成员,static成员的指针就是普通的指针。而non-static的成员指针需要特殊的语法——成员指针,成员指针包含类的类型以及成员的类型。一个定义类的成员指针例子如下:

#include <iostream>
using namespace std ;

class A
{
public:
A(int _p1 = 0, int _p2 = 0):publicValue(_p1), privateValue(_p2){} ;
~A(){} ;
void Foo(int value){ cout << value + publicValue << endl ;} ;
public:
int publicValue ;
private:
int privateValue ;
};

int main (int argc, char **argv)
{
//指向成员变量的指针
int A :: *pValue = &A :: publicValue ;
//int A :: *pValue = &A :: privateValue ; //编译错误,不能是私有成员变量

//指向成员函数的指针
void (A :: *pFun)(int) = &A :: Foo ;

A a ;
a.*pValue = 20 ;    //对a的publicValue进行赋值
(a.*pFun)(10) ;     //调用a的Foo函数
}


在这个例子中我们可以看出,定义成员变量和成员函数的指针的格式如下:

//定义类成员变量指针的格式
类型 类名 :: * 变量指针名 ;

//定义类成员函数指针的格式
返回值类型 (类名 :: * 函数指针名) (参数类型列表) ;


其中,成员函数指针必须在三个方面与它所指函数的类型保持一致:
(1)函数形参的类型和数目,包括成员是否为const。
(2)函数返回类型。
(3)函数所属的类。
注意:调用操作符的优先级高于成员指针操作,因此,调用的时候包围(类名 :: *)的括号不能省略。如:(a.*pFun)(10);不能写成a.*pFun(10);否则,编译出错。
这里出现了一个新的操作符.*
与之像对应,c++中也有操作符->*。它们类似于成员访问操作符.和->,它们能使我们将成员指针绑定到实际对象。这两个操作符的左操作数必须是类类型的对象或类类型的指针,右操作符是该类型的成员指针。(引用《C++ Primer》)
明白了这些,我们就可以动手写一个简单的C++类成员函数作为回调函数的例子了。

#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <string>
using namespace std ;

class A
{
typedef void (A :: *FUNCPTR)(void) ;
public:
A(string _n): name(_n){} ;
~A(){} ;
//注册一个回调函数
void registerCallBack(A *inst, FUNCPTR pFun) ;
//溢出一个回调函数
void removeCallBack(A *inst, FUNCPTR pFun) ;
//触发所有注册的回调函数
void trigger() ;

inline void funA() {cout << string(name).append("--A") << endl;} ;
inline void funB() {cout << string(name).append("--B") << endl;} ;
inline void funC() {cout << string(name).append("--C") << endl;} ;
private:
//判断一个回调函数是否已经被注册
size_t isIn(const pair<A*, FUNCPTR> &p) ;
string name ;
vector< pair<A*, FUNCPTR> > callBackList ;
};

size_t A :: isIn(const pair<A *, FUNCPTR> &p)
{
size_t index = -1 ;
for (size_t i = 0; i < callBackList.size(); ++ i)
if (p == callBackList[i])
{
index = i ;
break ;
}
return  index ;
}

void A :: registerCallBack(A *inst, FUNCPTR pFun)
{
pair<A*, FUNCPTR> addPair = make_pair(inst, pFun) ;
if (isIn(addPair) == -1)
callBackList.push_back(addPair) ;
}

void A :: removeCallBack(A *inst, FUNCPTR pFun)
{
pair<A*, FUNCPTR> rmPair = make_pair(inst, pFun) ;
size_t index = isIn(rmPair) ;
if (index != -1)
{
swap(callBackList[index], callBackList.back()) ;
callBackList.pop_back() ;
}
}

void A:: trigger()
{

for (size_t i = 0; i < callBackList.size(); ++ i)
{
A *pInst = callBackList[i].first ;
FUNCPTR pFun = callBackList[i].second ;
(pInst->*pFun)() ;
}
}

int main (int argc, char **argv)
{
A a("cat") ;
A b("dog") ;

cout << "Add a.funA, a.funB, a.funC" << endl ;
a.registerCallBack(&a, &A::funA) ;
a.registerCallBack(&a, &A::funB) ;
a.registerCallBack(&a, &A::funC) ;
a.trigger() ;

cout << "Remove a.funA" << endl ;
a.removeCallBack(&a, &A::funA) ;
a.trigger() ;

cout << "Add b.funA, b.funB" << endl;
a.registerCallBack(&b, &A::funA) ;
a.registerCallBack(&b, &A::funB) ;
a.trigger() ;

cout << "Remove a.funB, a.funC" << endl ;
a.removeCallBack(&a, &A::funB) ;
a.removeCallBack(&a, &A::funC) ;
a.trigger() ;
return  0 ;
}


输出结果:
dd a.funA, a.funB, a.funC
cat--A
cat--B
cat--C
Remove a.funA
cat--C
cat--B
Add b.funA, b.funB
cat--C
cat--B
dog--A
dog--B
Remove a.funB, a.funC
dog--A
dog--B

在C++中,信号与槽才是回调的完美解决方案,其实本质上是一个观察者模式

可以参看:http://www.cnblogs.com/dankye/archive/2012/08/25/2655816.html
OK,基本就是这样了,如果有错误之处还请大家指出,共同学习进步!

参考:
《C++ Primer》 http://blog.csdn.net/lvjinhua/article/details/349220 http://blog.csdn.net/jackystudio/article/details/11720325 http://my.oschina.net/apoptosis/blog/82572 http://www.dewen.org/q/4538/C%2B%2B%E4%B8%AD%E6%80%8E%E4%B9%88%E8%8E%B7%E5%8F%96%E7%B1%BB%E7%9A%84%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E7%9A%84%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%EF%BC%9F
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: