关于C++ 回调函数(callback)

1 关于回调函数

1.1 定义

什么是回调函数?我们绕点远路来回答这个问题。
编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写库;而应用编程就是利用写好的各种库来编写具某种功用的程序,也就是应用。系统程序员会给自己写的库留下一些接口,即API(application programming interface,应用编程接口),以供应用程序员使用。
所以在抽象层的图示里,库位于应用的底下。当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。
打个比方,你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。
下图列举了普通函数执行和回调函数调用的区别。

对于普通函数,就是按照实现设定的逻辑和顺序执行。
对于回调函数,假设Program A和Program B分别有两个人独立开发。回调函数Fun A2它是由Program A定义,但是由Program B调用。Program B只负责取调用Fun A2,但是不管Fun A2函数具体的功能实现。
在这里插入图片描述

1.2 为什么需要回调函数

因为有这样的使用场景,Fun A2只有在 Fun B1调用时才能执行,有点像中断函数的概念。那可能会有人问,在Program A中不断去查询Fun B1的状态,一旦它被执行了,就让Program A自己去执行Fun A2不行吗?如果你有这样的疑问,说明你已经入门了。
答案是“可以”,但是这样实现的方案不好。因为整个过程中Program A一直都在查询状态,非常耗资源,查询频率高了费CPU,查询频率低了实时性保证不了,Fun B1都执行好久了你才反应过来,Fun A2的执行就会明显晚于Fun B1了。正因为如此,回调函数才登上了舞台。

2 如何实现函数回调

函数的回调并不复杂,把 Fun A2的函数的地址/指针告诉Program B就可以了。
我们在这里要讨论的是在C++中,常用回调函数分为两大类:1.普通函数,2.类成员函数;

2.1 普通函数作为回调函数

#include <iostream>

void programA_FunA1() { printf("I'am ProgramA_FunA1 and be called..\n"); }

void programA_FunA2() { printf("I'am ProgramA_FunA2 and be called..\n"); }

void programB_FunB1(void (*callback)()) {
  printf("I'am programB_FunB1 and be called..\n");
  callback();
}

int main(int argc, char **argv) {
  programA_FunA1();

  programB_FunB1(programA_FunA2);
}

执行结果:
在这里插入图片描述

2.2 类成员函数又有两种类型:1.静态函数,2.非静态函数;

2.2.1 类的静态函数作为回调函数

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  static void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
};

class ProgramB {
 public:
  void FunB1(void (*callback)()) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    callback();
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(ProgramA::FunA2);
}

执行结果
在这里插入图片描述
可以看出,和调用普通回调函数没有什么本质的区别。
但这种实现有一个很明显的缺点:static 函数不能访问非static 成员变量或函数,会严重限制回调函数可以实现的功能。

2.2.2 类的非静态函数作为回调函数

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
};

class ProgramB {
 public:
  void FunB1(void (ProgramA::*callback)(), void *context) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    ((ProgramA *)context->*callback)();
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(&ProgramA::FunA2, &PA);  // 此处都要加&
}

执行结果
在这里插入图片描述
这种方法可以得到预期的结果,看似完美,但是也存在明显不足。
比如在programB中FunB1还使用 programA的类型,也就我预先还要知道回调函数所属的类定义,当programB想独立封装时就不好用了

下面还有一种方法可以避免这样的问题,可以把非static的回调函数 包装为另一个static函数,这种方式也是一种应用比较广的方法。

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA2Wrapper(void *context) {
    printf("I'am ProgramA.FunA2Wrapper() and be called..\n");
    ((ProgramA *)context)->FunA2();  // 此处调用的FunA2()是context的函数, 不是this->FunA2()
  }
};

class ProgramB {
 public:
  void FunB1(void (ProgramA::*callback)(), void *context) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    ((ProgramA *)context->*callback)();
  }

  void FunB2(void (*callback)(void *), void *context) {
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback(context);
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(&ProgramA::FunA2, &PA);  // 此处都要加&

  printf("\n");
  PB.FunB2(ProgramA::FunA2Wrapper, &PA);
}

执行结果:
在这里插入图片描述
这种方法相对于上一种,ProgramB中没有ProgramA的任何信息了,是一种更独立的实现方式。
FunB2()通过调用FunA2Wrapper(),实现间接的对FunA2()的调用。FunA2()可以访问和调用对类内的任何函数和变量。多了一个wrapper函数,也多了一些灵活性。​

上面借助wrapper函数实现回调,虽然很灵活,但是还是不够优秀,比如:
1)多了一个不是太有实际用处的wrapper函数。
2)wrapper中还要对传入的指针进行强制转换。
3)FunB2调用时,不但要指定wrapper函数的地址,还要传入PA的地址。​

那是否有更灵活、直接的方式呢?有,可以继续往下看。

3 std::funtion和std::bind的使用

std::funtion和std::bind可以登场了。
std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。
std::bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。
关于他们的详细用法可以自行百度,如果有需要的以后出一期单独写,这里直接上代码,看std::funtion和std::bind如何在回调中使用。

#include <iostream>

#include <functional> // fucntion/bind

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA3() { printf("I'am ProgramA.FunA3() and be called..\n"); }
};

class ProgramB {
  typedef std::function<void ()> CallbackFun;
 public:
   void FunB1(CallbackFun callback) {
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback();
  }
};

void normFun() { printf("I'am normFun() and be called..\n"); }

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  printf("\n");
  ProgramB PB;
  PB.FunB1(normFun);
  printf("\n");
  PB.FunB1(ProgramA::FunA3);
  printf("\n");
  PB.FunB1(std::bind(&ProgramA::FunA2, &PA));
}

执行输出:
在这里插入图片描述
std::funtion支持直接传入函数地址,或者通过std::bind指定。
简而言之,std::funtion是定义函数类型(输入、输出),std::bind是绑定特定的函数(具体的要调用的函数)。​

相比于wrapper方法,这个方式要更直接、简洁很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++回调函数是指将一个函数作为参数传递给另一个函数,并在需要的时候调用该函数。回调函数的用法在C++非常常见,特别是在事件处理、异步编程和库开发。下面是C++回调函数的用法的一些示例: 1. 函数指针作为回调函数[^1]: ```cpp #include <iostream> void callbackFunction(int value) { std::cout << "Callback function called with value: " << value << std::endl; } void performOperation(int value, void (*callback)(int)) { // 执行某些操作 callback(value); // 调用回调函数 } int main() { performOperation(10, callbackFunction); return 0; } ``` 2. 函数对象作为回调函数[^2]: ```cpp #include <iostream> class CallbackClass { public: void operator()(int value) { std::cout << "Callback function called with value: " << value << std::endl; } }; void performOperation(int value, const CallbackClass& callback) { // 执行某些操作 callback(value); // 调用回调函数对象 } int main() { CallbackClass callback; performOperation(10, callback); return 0; } ``` 3. 使用std::function作为回调函数: ```cpp #include <iostream> #include <functional> void callbackFunction(int value) { std::cout << "Callback function called with value: " << value << std::endl; } void performOperation(int value, const std::function<void(int)>& callback) { // 执行某些操作 callback(value); // 调用回调函数 } int main() { std::function<void(int)> callback = callbackFunction; performOperation(10, callback); return 0; } ``` 这些是C++回调函数的一些常见用法。通过回调函数,我们可以实现更加灵活和可扩展的代码结构。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值