C++类与回调函数

C++类与回调函数

从C的回调函数说起

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

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

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

#include <stdio.h>

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 <iostream>

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 <iostream>

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 <iostream>
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 <iostream>
#include <functional>
// typedef std::function<void(int)> 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的功能不止这些,这里介绍的只是他们的一种用法。

欢迎关注我的公众号
在这里插入图片描述

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
C++中,可以使用函数指针或者std::function来实现跨调用回调函数。具体实现方式如下: 1. 函数指针 定义一个函数指针型,该函数指针型指向回调函数型,例如: ``` typedef void (*CallbackFunc)(int); ``` 这里定义了一个名为CallbackFunc的函数指针型,该函数指针型指向一个参数型为int,返回型为void的回调函数。 在回调函数所在的中定义一个成员函数,该成员函数接受一个函数指针作为参数,并在需要调用回调函数时使用该函数指针,例如: ``` class CallbackClass { public: void setCallback(CallbackFunc func) { m_callbackFunc = func; } void doSomething() { if (m_callbackFunc) { m_callbackFunc(123); } } private: CallbackFunc m_callbackFunc; }; ``` 在其他中定义一个成员函数,该成员函数作为回调函数,并将其地址传递给CallbackClass的setCallback函数,例如: ``` class OtherClass { public: void callbackFunc(int value) { // do something with value } void doSomethingWithCallback() { CallbackClass callbackObj; callbackObj.setCallback(callbackFunc); callbackObj.doSomething(); } }; ``` 在上面的代码中,OtherClass中的callbackFunc函数就是回调函数,该函数将被CallbackClass调用。 2. std::function std::function是C++11标准中引入的函数对象,可以用来包装任何可调用对象,包括函数指针、成员函数指针、Lambda表达式等。使用std::function可以更加灵活地实现跨调用回调函数。 定义一个std::function型的变量,该变量的型与回调函数型一致,例如: ``` std::function<void(int)> callbackFunc; ``` 在回调函数所在的中定义一个成员函数,该成员函数接受一个std::function型的参数,并在需要调用回调函数时使用该参数,例如: ``` class CallbackClass { public: void setCallback(std::function<void(int)> func) { m_callbackFunc = func; } void doSomething() { if (m_callbackFunc) { m_callbackFunc(123); } } private: std::function<void(int)> m_callbackFunc; }; ``` 在其他中定义一个成员函数,该成员函数作为回调函数,并将其包装成std::function型的变量,然后将该变量传递给CallbackClass的setCallback函数,例如: ``` class OtherClass { public: void callbackFunc(int value) { // do something with value } void doSomethingWithCallback() { CallbackClass callbackObj; callbackObj.setCallback(std::bind(&OtherClass::callbackFunc, this, std::placeholders::_1)); callbackObj.doSomething(); } }; ``` 在上面的代码中,std::bind函数将OtherClass的callbackFunc函数绑定到当前对象上,并将其包装成std::function型的变量,然后将该变量传递给CallbackClass的setCallback函数。在回调函数中,std::placeholders::_1表示回调函数的第一个参数。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chasentech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值