C++回调函数理解

news2024/11/25 10:54:37

C++回调函数理解

    • 0.引言
    • 1.回调函数的实现方式
    • 2.普通函数以函数指针的形式进行实现
    • 3.类成员函数以静态函数进行实现
    • 4.类成员函数以非静态函数进行实现
    • 5.std::funtion和std::bind的使用
    • 6.c++回调的实现
    • 7.应用实例

0.引言

看了一些介绍感觉太官方了,我的简单理解就是从模式设计思想出发,回调函数的目的就是为了将变化的模块抽离出来,延迟实现;固定的模块抽象出来设计好,“写死”。

请添加图片描述

  • 参考博客1
  • 参考博客2

1.回调函数的实现方式

在这里插入图片描述

2.普通函数以函数指针的形式进行实现

#include <iostream>

// ===========不变的模块,写死================
// 回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}


// =========变化的模块,放到客户端进行实现========
// 回调函数的实现
void Callback(int result) {
    std::cout << "Callback received result: " << result << std::endl;
    // 执行回调操作...
}

int main() {
    int value = 5;

    // 调用带回调函数参数的函数
    PerformOperation(value, Callback);

    return 0;
}

3.类成员函数以静态函数进行实现

#include <iostream>

// ===========不变的模块,写死================
// 定义回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}


// =========变化的模块,放到客户端进行实现========
// 回调类
class CallbackClass {
public:
	// 类的静态成员函数,当做全局函数使用
    static void Callback(int result) {
        std::cout << "Callback received result: " << result << std::endl;
        // 执行回调操作...
    }
};

int main() {
    int value = 5;
    // 创建回调类的对象
    CallbackClass callbackObj;
    // 将静态成员函数作为回调函数
    CallbackFunction callback = CallbackClass::Callback;
    // 调用带回调函数参数的函数
    PerformOperation(value, callback);
    return 0;
}

可以看出,以上两种方式没有什么本质的区别。

但这种实现有一个很明显的缺点:static 函数不能访问非static 成员变量或函数,会严重限制回调函数可以实现的功能。

4.类成员函数以非静态函数进行实现

  • 实现1
#include <iostream>
// ===========不变的模块,写死================
// 定义回调函数类型
typedef void (*CallbackFunction)(int);
// 带回调函数参数的函数
void PerformOperation(int value, CallbackFunction callback) {
    std::cout << "Performing operation with value: " << value << std::endl;
    // 执行某些操作...

    // 调用回调函数
    callback(value * 2);
}

// =========变化的模块,放到客户端进行实现========
// 回调类
class CallbackClass {
public:
    void Callback(int result) {
        std::cout << "Callback received result: " << result << std::endl;
        // 执行回调操作...
    }
};

int main() {
    int value = 5;

    // 创建回调类的对象
    CallbackClass callbackObj;
    // 将非静态成员函数作为回调函数
    CallbackFunction callback = [&](int result) {
        callbackObj.Callback(result);
    };
    // 调用带回调函数参数的函数
    PerformOperation(value, callback);

    return 0;
}
  • 实现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);  // 此处都要加&
}

这种实现方式有很明显的缺点,不变的模块需要提前知道客户端的类型,显示不是很合理。

  • 实现3

这里还有一种方法可以避免这样的问题,可以把非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 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.FunB2(ProgramA::FunA2Wrapper, &PA);
}

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

5.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是绑定特定的函数(具体的要调用的函数)。

6.c++回调的实现

#include <functional>
#include <iostream>
// ===========不变的模块,写死================
class MyTest{
public:
    MyTest() = default;
    void doCalc(){
        //干其他事,完了
        // 执行回调
        if(myCallBack!= nullptr){
            myCallBack(1,2);
        }
    }

    using callback_t = std::function<void(const int &a, const int &b)>;

    // 注册回调
    void setCallBackHandler(const callback_t &cb){
        myCallBack = cb;
    }

private:
    // 定义回调
    callback_t myCallBack;
};

// =========变化的模块,放到客户端进行实现========
// 回调函数
void handleCallBack(const int &a,const int &b){
    std::cout << "this is from callback handleCallBack"<<std::endl;
}

int main(){

    MyTest t;

    // 回调函数
    auto f= [](const int &a,const int &b){
        std::cout << "this is from callback f"<<std::endl;
    };
    // 注册回调
    // 写法一
    t.setCallBackHandler(f);

    // 写法二
    t.setCallBackHandler([&f](auto &&a, auto &&b) {
        f(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    // 写法三
    t.setCallBackHandler([](auto &&a, auto &&b) {
        handleCallBack(std::forward<decltype(a)>(a), std::forward<decltype(b)>(b));
    });

    t.doCalc();
}

7.应用实例

  • 摘抄自高博的代码。

(1)写死的模块

  • io_utils.h
#include <fstream>
#include <functional>
#include <utility>

class TxtIO {
   public:
    TxtIO(const std::string &file_path) : fin(file_path) {}

    /// 定义回调函数
    using IMUProcessFuncType = std::function<void(const IMU &)>;
    using OdomProcessFuncType = std::function<void(const Odom &)>;
    using GNSSProcessFuncType = std::function<void(const GNSS &)>;

    TxtIO &SetIMUProcessFunc(IMUProcessFuncType imu_proc) {
        imu_proc_ = std::move(imu_proc);
        return *this;
    }

    TxtIO &SetOdomProcessFunc(OdomProcessFuncType odom_proc) {
        odom_proc_ = std::move(odom_proc);
        return *this;
    }

    TxtIO &SetGNSSProcessFunc(GNSSProcessFuncType gnss_proc) {
        gnss_proc_ = std::move(gnss_proc);
        return *this;
    }

    // 遍历文件内容,调用回调函数
    void Go();

   private:
    std::ifstream fin;
    IMUProcessFuncType imu_proc_;
    OdomProcessFuncType odom_proc_;
    GNSSProcessFuncType gnss_proc_;
};
  • io_utils.cc
#include "io_utils.h"
#include <glog/logging.h>

void TxtIO::Go() {
    if (!fin) {
        LOG(ERROR) << "未能找到文件";
        return;
    }

    while (!fin.eof()) {
        std::string line;
        std::getline(fin, line);
        if (line.empty()) {
            continue;
        }

        if (line[0] == '#') {
            // 以#开头的是注释
            continue;
        }

        // load data from line
        std::stringstream ss;
        ss << line;
        std::string data_type;
        ss >> data_type;

        if (data_type == "IMU" && imu_proc_) {
            double time, gx, gy, gz, ax, ay, az;
            ss >> time >> gx >> gy >> gz >> ax >> ay >> az;
            // imu_proc_(IMU(time, Vec3d(gx, gy, gz) * math::kDEG2RAD, Vec3d(ax, ay, az)));
            imu_proc_(IMU(time, Vec3d(gx, gy, gz), Vec3d(ax, ay, az)));
        } else if (data_type == "ODOM" && odom_proc_) {
            double time, wl, wr;
            ss >> time >> wl >> wr;
            odom_proc_(Odom(time, wl, wr));
        } else if (data_type == "GNSS" && gnss_proc_) {
            double time, lat, lon, alt, heading;
            bool heading_valid;
            ss >> time >> lat >> lon >> alt >> heading >> heading_valid;
            gnss_proc_(GNSS(time, 4, Vec3d(lat, lon, alt), heading, heading_valid));
        }
    }

    LOG(INFO) << "done.";
}

把读取文件的操作抽象出来写死,具体怎么处理留给回调函数(客户端)进行实现。

(2)变化的模块

    /// 记录结果
    auto save_result = [](std::ofstream& fout, double timestamp, const Sophus::SO3d& R, const Vec3d& v,
                          const Vec3d& p) {
        auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) { fout << v[0] << " " << v[1] << " " << v[2] << " "; };
        auto save_quat = [](std::ofstream& fout, const Quatd& q) {
            fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
        };

        fout << std::setprecision(18) << timestamp << " " << std::setprecision(9);
        save_vec3(fout, p);
        save_quat(fout, R.unit_quaternion());
        save_vec3(fout, v);
        fout << std::endl;
    };

    std::ofstream fout("./data/ch3/state.txt");
    io.SetIMUProcessFunc([&imu_integ, &save_result, &fout, &ui](const sad::IMU& imu) {
          imu_integ.AddIMU(imu);
          save_result(fout, imu.timestamp_, imu_integ.GetR(), imu_integ.GetV(), imu_integ.GetP());
          if (ui) {
              ui->UpdateNavState(imu_integ.GetNavState());
              usleep(1e2);
          }
      }).Go();

可以,还是很实用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/574544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D点云深度学习处理的基本概念

权重矩阵更新学习方法概述 参数初始化&#xff1a; 需要对权重矩阵初始化参数&#xff08;通常使用随机初始化方法&#xff0c;如正态分布或者均匀分布生成随机数&#xff09; 前向传播&#xff1a; 前向传播中&#xff0c;模型计算权重矩阵和输入数据的结果&#xff0c;得…

第1关:JDBC程序设计

第1关&#xff1a;JDBC程序设计 任务描述相关知识JDBC程序设计JDBC主要功能JDBC主要接口和函数JDBC应用程序开发实例编程要求代码参考 任务描述 本关任务&#xff1a;使用 JDBC 往表中插入数据。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 1.JDBC 主要功…

SM3_CNC,轴组,G代码解析,CNC运动控制

硬件要求&#xff1a; 中型PLC&#xff08;汇川AM600&#xff0c;禾川HCQ0&#xff09;&#xff0c;且带 SM3_CNC.library 库&#xff08;3.5.6支持离线仿真&#xff09; G代码标准&#xff1a; DIN66025 DIN66025-1标准G0 运动定位 G1 线性插补 G2 顺圆插补 G3 …

决策树案例以及决策树可视化

一、案例背景 泰坦尼克号沉没是历史上最臭名昭著的沉船之一。1912年4月15日&#xff0c;在她的处女航中&#xff0c;泰坦尼克号在与冰山相撞后沉没&#xff0c;在2224名乘客和机组人员中造成1502人死亡。在这个案例中&#xff0c;我们要求完成对哪些人可能存活的分析。特别是&…

【C++系列Pn】引用——背刺指针的神秘刺客“吃我一棍”

前言 大家好吖&#xff0c;欢迎来到 YY 滴 C系列 &#xff0c;热烈欢迎&#xff01;如标题所示&#xff0c;本章主要内容主要来侃侃“引用”这个刺客&#xff01;如下就是大纲啦~ 一.引用 1.含义与特点 引用&#xff0c;即取别名。它的最大特点是编译器不会为引用变量而开辟空间…

202313读书笔记|《山居七年》————我只想在广袤璀璨的星河里享受生的鲜活,独自飞,游走

202313读书笔记|《山居七年》————我只想在广袤璀璨的星河里享受生的鲜活&#xff0c;独自飞&#xff0c;游走 《山居七年》 作者张二冬&#xff0c;选择隐士山居是一种很自由随性的生活态度&#xff0c;我觉得这不是普通人可以拥有的&#xff0c;比如我&#xff0c;并未入…

Office project 2007安装

哈喽&#xff0c;大家好。今天一起学习的是project 2007的安装&#xff0c;Microsoft Office project项目管理工具软件&#xff0c;凝集了许多成熟的项目管理现代理论和方法&#xff0c;可以帮助项目管理者实现时间、资源、成本计划、控制。有兴趣的小伙伴也可以来一起试试手。…

xmind思维导图转成禅道测试用例

xmind思维导图用来编写用例胜在效率高&#xff0c;可以快速的理清楚需求。缺点在于没法上传禅道&#xff0c;但是公司有要求每次迭代要有禅道的测试用例。直接写禅道的测试用例的话&#xff0c;又很耗时&#xff0c;效率低下&#xff0c;与提高人效的公司年度战略不符&#xff…

apache虚拟主机头的实现方式

目录 一&#xff1a;基于不同的IP地址 二&#xff1a;基于不同的端口 三&#xff1a;基于不同的域名 一&#xff1a;基于不同的IP地址 步骤一&#xff1a;修改主配置文件 vim /usr/local/apache/conf/httpd.conf 声明监听不同的IP地址的80端口 Listen 192.168.2.20:80 Listen…

ChatGPT工作提效之使用python开发对接百度地图开放平台API的实战方案(批量路线规划、批量获取POI、突破数量有限制、批量地理编码)

ChatGPT工作提效系列文章目录 ChatGPT工作提效系列 ChatGPT工作提效之初探路径独孤九剑遇强则强ChatGPT工作提效之在程序开发中的巧劲和指令(创建MySQL语句、PHP语句、Javascript用法、python的交互)ChatGPT工作提效之生成开发需求和报价单并转为Excel格式ChatGPT工作提效之小…

URLConnection(四)

文章目录 1. 配置客户端请求HTTP首部2. 向服务器写入数据3. URLConnection的安全考虑4. HttpURLConnection 1. 配置客户端请求HTTP首部 HTTP客户端&#xff08;如浏览器&#xff09;向服务器发送一个请求行和一个首部&#xff0c;如下&#xff1a; web服务器可以根据这个信息…

Visual Studio 远程调试工具(Remote Debugger)使用方法

一、关于Remote Debugger 当项目在测试环境上有bug&#xff0c;需要运行代码调试一下&#xff0c;这时就需要在测试环境上安装一个调试工具&#xff08;Remote Debugger&#xff09;&#xff0c;然后在本地运行代码&#xff0c;远程链接到测试环境服务器来调试代码&#xff1b…

算法基础学习笔记——⑦位运算

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;算法基础学习 目录 ✨位运算 ✨操作一 &#x1f353;十进制转化成二进制、八进制、十六进制&#xff08;连除法&#xff09; &#x1f353;二进制、八进制、十六进制转化成十进制 &#x1f353;关于原码&#xff0c;反码&am…

Unity四叉树地图

当使用Unity构建大规模的游戏地图或场景时&#xff0c;使用四叉树数据结构可以提高性能和效率。四叉树是一种基于分割的数据结构&#xff0c;将空间划分为四个相等的子区域&#xff0c;并以递归方式构建树结构。在游戏开发中&#xff0c;四叉树常用于空间分区、碰撞检测和可视化…

Eclipse 教程Ⅱ

Eclipse 修改字符集 默认情况下 Eclipse 字符集为 GBK&#xff0c;但现在很多项目采用的是 UTF-8&#xff0c;这是我们就需要设置我们的 Eclipse 开发环境字符集为 UTF-8&#xff0c; 设置步骤如下&#xff1a; 在菜单栏选择 Window -> Preferences -> General -> W…

python的统计函数库scipy.stats是一个很棒的统计包

描述&#xff1a;在Scipy的模块中&#xff0c;有一个统计的木块——States,其中函数有描述统计(describe)、峰度(kurtosis)、偏度(skew)、众数(mode)、n阶矩。 分布&#xff1a;还有一些分布函数&#xff0c;正泰分布(norm)、对数正态分布(lognorm)、…… 计算&#xff1a;针…

Go语言介绍以及Go语言环境安装

初步介绍&#xff1a; Go 是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变得容易。 Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发&#xff0c;后来还加入了Ian Lance Taylor, Russ Cox等人&#xff0c;并最终于2009年11月开源&am…

Manjaro linux 安装svn 并在文件管理器里显示相关图标

需要先安装svn linux版打开终端执行 1 sudo pacman -S svn 安装完成后执行一下 1 svn --version 出现这个就说明svn已经安装完成了&#xff0c;这个时候我们可以执行 1 svn checkout [路径] 就可以检出svn服务器上相关内容了 但是这个有的时候我们打开文件管理器想要看到被…

并发编程的三大特性之原子性

原子性 这个定义是一种规定&#xff0c;描述了Java中的理想就是为了能实现一个操作不能分割&#xff0c;不可中断&#xff0c;一个线程在执行的时候&#xff0c;另一个线程不会去影响他。 Java中的原子性可以理解为多线程操作临界资源&#xff0c;预期的结果和最终的结果一致。…

STM32基本外设超详细44000字教程

GPIO GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口可配置为8种输入输出模式引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等输入模式下可…