0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

介绍在C++中实现回调的几种方法

冬至子 来源:技术铺子 作者: chasenzhang 2023-01-18 15:09 次阅读

从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 functionint main()’:
test_class_cb.cpp:15:14: error: invalid use of non-static member functionvoid 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
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语言
    +关注

    关注

    180

    文章

    7598

    浏览量

    136188
  • 编译器
    +关注

    关注

    1

    文章

    1618

    浏览量

    49050
  • 回调函数
    +关注

    关注

    0

    文章

    87

    浏览量

    11543
收藏 人收藏

    评论

    相关推荐

    Android NDK编程--- C/C++调用Java不同类的静态方法

    前言上一篇我们介绍了《Android NDK编程(四)--- C/C++调用Java方法》,主要是C
    发表于 07-02 07:56

    函数程序开发中有何作用呢

    函数程序开发是一个非常重要的概念,所谓的其实就是不同程序模块之间的接口和约定,是软件
    发表于 03-01 07:13

    如何在screenViewBase写这两个

    你好我想使用 clicklistener 和按钮。如何在screenViewBase写这两个?screenViewBase::sc
    发表于 01-30 07:36

    C/C++函数

    ;#125;在这个入门案例,Callback_1、2、3就是函数,handle函数的第二个参数就是函数指针,也就是通过函数指针来调用。纯C语言通过函数指针来进行
    发表于 02-11 15:25

    (纯干货)使用STM32测量频率和占空比的几种方法

    本文详细介绍了使用STM32测量频率和占空比的几种方法
    的头像 发表于 03-13 15:43 4.3w次阅读
    (纯干货)使用STM32测量频率和占空比的<b class='flag-5'>几种方法</b>

    VISUAL C++教程之VISUAL C++的安装和使用方法

    本文档的主要内容详细介绍的是VISUAL C++教程之VISUAL C++的安装和使用方法资料免费下载。
    发表于 12-27 16:32 19次下载
    VISUAL <b class='flag-5'>C++</b>教程之VISUAL <b class='flag-5'>C++</b>的安装和使用<b class='flag-5'>方法</b>

    干货:计算fibnacci 级数的几种方法

    干货:计算fibnacci 级数的几种方法
    的头像 发表于 06-22 11:23 2396次阅读
    干货:计算fibnacci 级数的<b class='flag-5'>几种方法</b>

    详解函数的概念及使用步骤

    函数就是一个被作为参数传递的函数。C语言中,函数只能使用函数指针
    的头像 发表于 05-26 15:20 3978次阅读

    一文详解C/C++函数

    参数传递给另一个函数,函数不是由该函数的实现方直接调用,而是特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。**如果代码立即被执行就称为同步
    的头像 发表于 02-12 09:20 1511次阅读

    labview事件的使用

    介绍LabVIEW事件的使用方法,可以用于处理执行时间较长的异步事件
    的头像 发表于 08-24 16:38 4114次阅读
    labview<b class='flag-5'>中</b>事件<b class='flag-5'>回</b><b class='flag-5'>调</b>的使用

    C++生成Dll与函数测试

    描述了VS环境下,通过C++生成dll的方法,测试函数
    的头像 发表于 08-29 16:05 1518次阅读
    <b class='flag-5'>C++</b>生成Dll与<b class='flag-5'>回</b><b class='flag-5'>调</b>函数测试

    javajvm优有几种方法

    JVM优是Java应用程序性能优化过程的重要步骤,它通过针对JVM进行优化来提高应用程序的性能和可靠性。JVM优可以根据具体的场景和需求,采用不同的方法和策略进行。 首先,我们需
    的头像 发表于 12-05 11:11 2059次阅读

    ​​嵌入式函数的实现方法

    函数的命名规范没有固定的标准,但是根据通用惯例和编码规范,函数的命名应该能够反映函数的作用和功能,让其他开发者能够快速理解并使用。
    发表于 03-04 14:49 647次阅读

    函数(callback)是什么?函数的实现方法

    函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。函数通常用于事件处理、异步编程和处理各种操作系统和框架的API。
    发表于 03-12 11:46 2715次阅读

    C++实现类似instanceof的方法

    函数,可实际上C++没有。但是别着急,其实C++中有两种简单的方法可以实现类似Java的in
    的头像 发表于 07-18 10:16 534次阅读
    <b class='flag-5'>C++</b><b class='flag-5'>中</b><b class='flag-5'>实现</b>类似instanceof的<b class='flag-5'>方法</b>