C++:回调函数的应用

本文关于回调函数的学习的学习资料都来自于这里

原文写得非常详细,所以将原文的大部分内容作为笔记摘抄了过来。

文章目录

1.回调函数

回调函数指的是通过将一个函数作为参数传递给另一个函数,使得该函数能在特定事件发生或满足某个条件时被调用的机制。

通常情况下,回调函数会在某个特定的上下文中被注册,并在满足特定条件时由相应的代码调用。这种机制常用于异步编程、事件处理、用户交互、错误处理等场景,以便将控制权从调用方传递给被调用方。

以下是回调函数的一般使用方式:

定义回调函数:定义一个函数,其签名和参数与所需的回调函数匹配。

注册回调函数:将定义好的回调函数作为参数传递给需要使用回调函数的函数或组件。

触发回调:当满足触发条件时,调用方会触发回调函数,并将必要的参数传递给回调函数。

执行回调:回调函数被调用,执行相应的操作,并返回结果(如果有必要)。

通过回调函数,可以实现不同模块之间的解耦,让调用方在满足特定条件时,将具体的行为交给被调用方来处理。这种灵活性和可扩展性使得回调函数成为处理异步和事件驱动的重要工具。

1.1普通函数作为回调函数

cpp 复制代码
#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);//传入函数名
}

执行结果

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

cpp 复制代码
#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 成员变量或函数,会严重限制回调函数可以实现的功能。

1.3类的非静态成员函数作为回调函数

cpp 复制代码
#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想独立封装时就不好用了。

1.4优化--非静态包装为静态

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

cpp 复制代码
#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的地址。

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

2. std::funtion和std::bind的使用

std::funtion和std::bind可以登场了。
std::function是一种通用、多态的函数封装 。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。std::bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。

cpp 复制代码
#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));
}

执行输出:

相关推荐
aramae21 小时前
C++ -- 模板
开发语言·c++·笔记·其他
MChine慕青1 天前
顺序表与单链表:核心原理与实战应用
linux·c语言·开发语言·数据结构·c++·算法·链表
骄傲的心别枯萎1 天前
RV1126 NO.16:通过多线程同时获取H264和H265码流
linux·c++·音视频·rv1126
落羽的落羽1 天前
【C++】特别的程序错误处理方式——异常机制
开发语言·c++
空山新雨(大队长)1 天前
C 语言第一课:hello word c
c++·c·exe
春蕾夏荷_7282977251 天前
c++ 第三方库与个人封装库
c++·三方库
牵牛老人1 天前
Qt C++ 复杂界面处理:巧用覆盖层突破复杂界面处理难题之一
数据库·c++·qt
离越词1 天前
C++day8作业
开发语言·c++·windows
MMjeaty1 天前
deque容器
c++
CYRUS_STUDIO1 天前
如何防止 so 文件被轻松逆向?精准控制符号导出 + JNI 动态注册
android·c++·安全