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

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


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页