C++类与回调函数

C++类与回调函数

从C的回调函数说起

在C语言中,回调函数是一个非常重要的概念,它的定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

其实说白了就是把一个函数当做参数传下去。
C中实现回调函数比较简单,请看下面这个例子:

#include 

typedef void (*p_CB)(int);
void CB(int a)              { printf("CB a = %d\n", a); }
void runCB(p_CB CB, int a)  { printf("run CB\n"); CB(a); }

int main()
{
    p_CB cb = &CB;
    runCB(cb, 20);
    return 0;
}

C++中的回调函数的问题

在C++中的一个重要概念就是类,所以我们一般想让类的成员函数作为回调函数(如果直接用非类的成员函数作为回调函数,其实就和C语言中的方法一样),但是想实现这样的功能,还是存在一些限制的。主要原因是,类的成员函数的参数都隐藏了this指针这个参数,既然多加了一个参数,那么成员函数的参数个数就和定义的回调函数个数不匹配,导致调用失败。
举个例子:

#include 

typedef void (*p_CB)(int);
void runCB(p_CB CB, int a) { printf("run CB\n"); CB(a); }

class B
{
public:
    void CB(int a) { printf("CB a = %d\n", a); }
};

int main()
{
    B b;
    runCB(B::CB, 20); //错误
    return 0;
}

我们这样写,在15行会报下面的错误。

test_class_cb.cpp: In function ‘int main():
test_class_cb.cpp:15:14: error: invalid use of non-static member function ‘void B::CB(int)runCB(B::CB, 20); //错误

报的错说使用了非静态的函数,分析下原因,其实是B::CB的函数是非静态成员函数,它隐藏了this参数,导致p_CB和B::CB这两个函数的参数不匹配。

而编译器报的这个错误,其实也是同样的意思,编译器说我们调用的不是非静态函数,当我们把B::CB定义成静态函数也就不会报错了(也就是去掉了this指针)。

C++中的回调函数的实现

静态函数

既然成员函数都会存在this指针,那么我们可以去掉this指针呢?其实是可以的,我们把成员函数定义为静态函数,静态函数是属于整个类的,不属于某个对象,也就没有this指针了。

这个例子的话,就是我们在上一个章节提到的,大家可以直接加上static。

这种方法其实有一定的缺点,静态会带来诸多的不变,因为静态函数没有this指针,那么它就访问不了普通的成员函数或者数据成员,会给我们在开发中带来不小的影响,特别是工程比较大的时候。

成员函数的函数指针

这个方法从另外一个角度出发,我们可以定义一个成员函数的函数指针,其实相当于这个函数指针有了this参数了。

例子如下:

#include 

class B
{
public:
    void CB(int a) { printf("CB a = %d\n", a); }
};

typedef void (B::*p_CB)(int);
// using p_CB = void (B::*)(int);
void runCB(p_CB CB, B b, int a)  { printf("run CB\n"); (b.*CB)(a); }

int main()
{
    B b;
    p_CB p_f1 = &B::CB;
    runCB(p_f1, b, 20);
    return 0;
}

这种写法晦涩难懂,特别是(b.*CB)(a)这种写法一看上去就觉得很奇怪,代码写的越生僻,越复杂,带来的风险就越大。另外,如果想要采用这种方法实现回调,那么在定义回调函数的时候就得知道谁会调用它,显然,这个要求是不合理的,所以,这种方法很不常用,只是介绍这种方法。

单例模式

这个方法是上面静态函数方法的进一步优化,因为静态函数没有传进去this指针,那么我们可不可以想办法得到this指针呢?单例模式就可以做到,一般上单例模式为了让类只存在一个对象,一般都会在类的内部定义一个指向自己的指针,我们就可以通过这个指针,在任何地方都可以访问到这个对象。

简单例子:

#include 
class B
{
public:
    static B *get_instance() {
        static B instance;
        return &instance;
    }
    void CB(int a) { printf("CB a = %d\n", a); }
};
void runCB(int a)
{
    printf("run CB\n"); 
    B *b_ptr = B::get_instance();
    b_ptr->CB(a);
}
int main(int argc, char *argv[])
{
    B *b_ptr = B::get_instance();
    runCB(20);
    return 0;
}

使用这种方法,在任何地方都可以访问该对象,可以很方便得调用对象的方法,是比较好的一种方法了,需要注意的就是单例模式的一些限制而已。另外说一下,单例模式只能说是一种可以解决回调函数的办法,不一定是因为要回调,才设计成单例。

C++11的function和bind

如果你的编译器支持C++11标准的话(现在编译器基本上都会支持),还有一种更加方便快捷的方法。

function模板

首先介绍一下什么叫可调用对象,简单来说就是可以使用()来调用的对象,在C++11中,包括函数、函数指针、lambda表达式、bind创建的对象和重载了()的类。

function是一个模板函数,它的功能和函数指针很像,但是函数指针只能针对非成员函数,而function则可以调用C++中所有的可调用实体。

对于成员函数来说,function不能直接调用,这就需要通过下面的bind来做“媒介”。

bind函数

bind函数简单介绍,就是可以绑定到任意一个可调用对象,下面是它的定义。

可以讲bind看作一个通用的函数适配器,他接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。----《c++Primer》

也就是说,通过bind绑定类的非静态成员函数,生成一个可调用对象,然后通过function模板去调用这个可调用对象,也就实现了调用类的非静态成员函数。

下面通过一个简单的例子来说明如何使用。
例子

#include 
#include 
// typedef std::function funCB;
using funCB = std::function<void(int)>; //可以这样写,更直观
class B
{
public:
    void setCB(funCB CB)    { m_CB = CB; }
    void runCB(int a)       { std::cout << "runCB" << std::endl; m_CB(a); }
    void printB(int a)      { std::cout << "a = " << a << std::endl; }
    funCB m_CB;
};
class C
{
public:
    void cbfun(int a) { std::cout << "CB a = "
                        << a << std::endl; }
};
int main()
{
    B b; C c;
    funCB fun = std::bind(&C::cbfun, &c, std::placeholders::_1);
    b.setCB(fun);
    b.runCB(20);
    return 0;
}

在这个例子中,b.runCB(),然后会调用m_CB函数,而m_CB通过setCB绑定到了C::cbfun,所以C::cbfun也会被调用。也就是就是通过b的成函数调用到了c的成员函数,实现了回调。

这种方式是不是看起很简单呢,

后记

这边文章介绍了在C++中实现回调的几种方法,总的来说C++11的function&bind是比较好的方法,另外说一点,function的bind的功能不止这些,这里介绍的只是他们的一种用法。

欢迎关注我的公众号
C++类与回调函数_第1张图片

你可能感兴趣的:(C/C++)