当前位置: 首页 > news >正文

C++ bind复杂回调逻辑分析

 回调函数基本知识回顾

回调函数是什么

  • 函数指针或者函数对象作为参数传递给另一个函数的机制,当某个事件发生的时候,系统会自动的调用这些函数进行处理
  • 事件驱动模型中作用,回调函数则被用于处理I/O事件,通常用来读写异常等事件

bind 基本用法

  • 参数分析
    • function:绑定的函数或者成员函数
    • arg1\arg2等:绑定到函数的参数,同时也可以是具体的数值
    • 总结:调用bound_function的时候,bound_function会调用function,然后传递给function中arg1\arg2等对应参数 
  • 占位符
    • 首先占位符可以给具体参数,也就是向function函数中绑定具体的参数,同样也可以使用占位符,等待用户来传入数据
    • 注意,占位符是可以交换位置的,第二个事例可以说明该问题

 

#include <functional>
#include <iostream>void print(int a, int b) {std::cout << "a: " << a << ", b: " << b << std::endl;
}int main() {// 绑定 print 函数,第一个参数固定为 10,第二个参数使用占位符auto bound_print = std::bind(print, 10, std::placeholders::_1);bound_print(20); // 输出: a: 10, b: 20// 绑定 print 函数,交换参数位置auto bound_print_swap = std::bind(print, std::placeholders::_2, std::placeholders::_1);bound_print_swap(10, 20); // 输出: a: 20, b: 10return 0;
}

类的成员函数作为回调函数

使用lambada表达式,通过lambada表达式捕获当前对象的this指针,将该对象的成员函数进行封装。

#include <iostream>
#include <functional>class CallbackHandler {
public:void myCallback(int result) {std::cout << "Callback called with result: " << result << std::endl;}
};// 一个执行回调函数的函数
void performOperation(int x, std::function<void(int)> callback) {// 模拟某种操作int result = x * 2;// 调用回调函数callback(result);
}int main() {CallbackHandler handler;// 使用 lambda 捕获 this 指针,绑定成员函数performOperation(5, [&handler](int result) { handler.myCallback(result); });return 0;
}

std::function 封装可调用对象

利用std::function通用多态的函数封装器特性

#include <iostream>
#include <functional>// 一个执行回调函数的函数
void performOperation(int x, std::function<void(int)> callback) {// 模拟某种操作int result = x * 2;// 调用回调函数callback(result);
}// 一个回调函数
void myCallback(int result) {std::cout << "Callback called with result: " << result << std::endl;
}int main() {// 调用 performOperation 并传递 myCallback 作为回调函数performOperation(5, myCallback);return 0;
}

using与回调函数

using关键字用来定义类型,在回调函数的使用上,用来定义回调函数类型的别名,代码更好懂些

#include <iostream>
#include <functional>// 使用 using 关键字定义一个回调函数类型别名
using func_t = std::function<void(int)>;// 一个执行回调函数的函数
void performOperation(int x, func_t callback) {// 模拟某种操作int result = x * 2;// 调用回调函数callback(result);
}// 一个回调函数
void myCallback(int result) {std::cout << "Callback called with result: " << result << std::endl;
}int main() {// 调用 performOperation 并传递 myCallback 作为回调函数performOperation(5, myCallback);return 0;
}

bind绑定

bind绑定成员函数

  • 含义理解
    • 成员函数与对象实例绑定在一起,也就是将成员函数与this指针绑定在一起,从而确保回调函数可以访问到该对象的成员变量和其他成员函数
    • 简单理解,一个成员函数可能调用了该类中的其他函数或者成员变量,bind的时候如果仅仅绑定了一个函数,那么它是无法访问类中的其他变量和函数,所以也就无法执行。就像学校通知你录取了(你与学校进行了绑定)但是学校门禁以及宿舍门禁不录入你的信息,那么你就没有权限在学校畅行。

#include <functional>
#include <iostream>class MyClass {
public:void print(int a, int b) const {std::cout << "a: " << a << ", b: " << b << std::endl;}
};int main() {MyClass obj;// 绑定成员函数auto bound_print = std::bind(&MyClass::print, &obj, std::placeholders::_1, std::placeholders::_2);bound_print(10, 20); // 输出: a: 10, b: 20return 0;
}

 绑定成员变量

  • 用法:使用bind绑定成员变量,返回的函数可以访问该成员变量
#include <functional>
#include <iostream>class MyClass {
public:int value;MyClass(int v) : value(v) {}
};int main() {MyClass obj(42);// 绑定成员变量auto bound_value = std::bind(&MyClass::value, &obj);std::cout << "Value: " << bound_value() << std::endl; // 输出: Value: 42return 0;
}

 bind 和 function结合使用

bind和function结合使用,在代码中能够实现逻辑更加清晰,可以更好的分辨回调函数模块。

绑定普通函数(简单了解其使用方法)

#include <functional>
#include <iostream>// 普通函数
void print(int a, int b) {std::cout << "a: " << a << ", b: " << b << std::endl;
}int main() {// 使用 std::bind 绑定普通函数,固定第一个参数为 10auto bound_print = std::bind(print, 10, std::placeholders::_1);// 使用 std::function 存储绑定后的函数std::function<void(int)> func = bound_print;func(20); // 输出: a: 10, b: 20return 0;
}

 绑定成员函数及其参数

#include <functional>
#include <iostream>class MyClass {
public:void print(int a, int b) const {std::cout << "a: " << a << ", b: " << b << std::endl;}
};int main() {MyClass obj;// 绑定成员函数,绑定对象为 obj,并固定第一个参数为 10auto bound_print = std::bind(&MyClass::print, &obj, 10, std::placeholders::_1);// 使用 std::function 存储绑定后的成员函数std::function<void(int)> func = bound_print;func(20); // 输出: a: 10, b: 20return 0;
}

复杂回调案例分析

定时器任务调度

(注意:该事例的分析是基于C++线程库进行举例,下文中有使用Linux中POSIX编程)

理解:给任务设定了一个定时回调,(该线程)到时间后就执行对应的函数。类似于绑定一个定时炸弹(任务+定时器)交给线程,这个定时炸弹到时间就爆炸。

详细分析

  • TimerScheduler类负责管理定时任务,到达指定时间就执行任务
  • MyServer则使用bind将task_callback成员函数绑定到this指针和占位符_1上,传递给TimerScheduler
  • 执行步骤分析
    • 使用TimerScheduler创建scheduler对象,然后利用scheduler对象构造一个MyService对象
    • 通过service对象(属于MyService类)的start方法,绑定回调函数task_callback(后面调试会验证此处的this指针就是service--通过this指针从而实现调用该类的成员函数
      • bind在该处作用分析:bind生成了一个可调用对象task,然后将调用MyService对象的task_callback方法,然后后面留出了一个参数占位符,等待传递参数
    • 将绑定的task再次传递给scheduler中的add_task函数(第二次bind)
      • scheduler.add_task(5, 1, std::bind(task, 42));
      • 该处bind创建了一个可调用对象,该对象使用的是task_callback函数,向该函数传入了一个42的参数,也就是该函数的task_callback的形参value是42
    • 然后利用service(MyService创建)对象传入的scheduler中的add_task方法,创建一个新的TimerTask对象
      • tasks.emplace_back(id, callback);(创建了一个新的对象,注意该任务的回调函数就是上面传入的bind(task,42)
      • TimerTask类中,有对应的ID和回调函数管理
    • 五秒后,调用之前设置的回调函数 

重点步骤模块汇总 

 事例源代码 

#include <functional>
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>class TimerTask {
public:TimerTask(int id, std::function<void()> callback): task_id(id), task_callback(callback) {}void execute() {if (task_callback) {task_callback();}}int get_id() const { return task_id; }private:int task_id;std::function<void()> task_callback;
};class TimerScheduler {
public:void add_task(int delay_sec, int id, std::function<void()> callback) {tasks.emplace_back(id, callback);std::thread([this, delay_sec, id]() {std::this_thread::sleep_for(std::chrono::seconds(delay_sec));this->execute_task(id);}).detach();}void execute_task(int id) {for (auto &task : tasks) {if (task.get_id() == id) {task.execute();break;}}}void run() {// Simulate running scheduler}private:std::vector<TimerTask> tasks;
};class MyService {
public:MyService(TimerScheduler &scheduler) : scheduler(scheduler) {}void start() {auto task = std::bind(&MyService::task_callback, this, std::placeholders::_1);scheduler.add_task(5, 1, std::bind(task, 42));}void task_callback(int value) {std::cout << "Task executed with value: " << value << std::endl;}private:TimerScheduler &scheduler;
};int main() {TimerScheduler scheduler;MyService service(scheduler);service.start();scheduler.run();std::this_thread::sleep_for(std::chrono::seconds(10)); // Simulate runningreturn 0;
}

 this指针所指向对象分析

LinuxPOSIX库实现定时器任务调度 

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <functional>class TimerTask {
public:TimerTask(int id, std::function<void()> callback): task_id(id), task_callback(callback) {}void execute() {if (task_callback) {task_callback();}}int get_id() const { return task_id; }private:int task_id;std::function<void()> task_callback;
};class TimerScheduler {
public:void add_task(int delay_sec, int id, std::function<void()> callback) {tasks.emplace_back(id, callback);TaskArgs *args = new TaskArgs{this, delay_sec, id};pthread_t thread;pthread_create(&thread, nullptr, thread_func, args);pthread_detach(thread);}void execute_task(int id) {for (auto &task : tasks) {if (task.get_id() == id) {task.execute();break;}}}void run() {for(int i = 0;i<10;i++){sleep(1);std::cout<<i<<":此时定时任务还没有执行"<<std::endl;}   // Simulate running scheduler}private:struct TaskArgs {TimerScheduler *scheduler;int delay_sec;int id;};static void* thread_func(void *arg) {TaskArgs *args = static_cast<TaskArgs*>(arg);sleep(args->delay_sec);args->scheduler->execute_task(args->id);delete args;return nullptr;}std::vector<TimerTask> tasks;
};class MyService {
public:MyService(TimerScheduler &scheduler) : scheduler(scheduler) {}void start() {auto task = std::bind(&MyService::task_callback, this, std::placeholders::_1);scheduler.add_task(5, 1, std::bind(task, 42));}void task_callback(int value) {std::cout << "Task executed with value: " << value << std::endl;}private:TimerScheduler &scheduler;
};int main() {TimerScheduler scheduler;MyService service(scheduler);service.start();scheduler.run();sleep(10); // Simulate runningreturn 0;
}

重要内容调试分析 

 

分析新线程创建后,该线程调用回调函数的具体逻辑(回调函数绑定不再叙述,参考上文内容)

 

 

 

异步任务处理器

 逻辑:与定时器的任务管理相似,任务管理器管理任务,任务绑定回调函数,任务添加到任务管理器后线程分离自己独自执行预先设置好的线程处理函数。(简单理解:公交车首先需要绑定自己是哪一个公交车调用中心的,公交车调度中心,给每一辆公交车都绑定一个具体的路线,然后让公交车自己去完成任务

主要步骤总结(下文详细进行流程分析)

  • 主函数 创建 AsyncTaskProcessorMyApplication 对象,启动应用程序,并添加两个任务。
  • AsyncTaskProcessor 创建工作线程,并在析构时确保线程安全退出。
  • 任务处理线程 等待任务队列中的新任务,处理任务,并执行任务回调。
  • 任务执行 打印任务完成消息
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>class AsyncTask {
public:AsyncTask(int id, std::function<void()> callback): task_id(id), task_callback(callback) {}void execute() {if (task_callback) {task_callback();}}int get_id() const { return task_id; }private:int task_id;std::function<void()> task_callback;
};class AsyncTaskProcessor {
public:AsyncTaskProcessor() : stop(false) {pthread_create(&worker_thread, nullptr, process_tasks, this);}~AsyncTaskProcessor() {{std::unique_lock<std::mutex> lock(mutex);stop = true;}cond_var.notify_all();pthread_join(worker_thread, nullptr);}void add_task(int id, std::function<void()> callback) {{std::unique_lock<std::mutex> lock(mutex);tasks.emplace(id, callback);}cond_var.notify_all();}private:static void* process_tasks(void* arg) {AsyncTaskProcessor* self = static_cast<AsyncTaskProcessor*>(arg);while (true) {AsyncTask task(0, nullptr);{std::unique_lock<std::mutex> lock(self->mutex);self->cond_var.wait(lock, [self] { return !self->tasks.empty() || self->stop; });if (self->stop && self->tasks.empty()) {break;}task = std::move(self->tasks.front());self->tasks.pop();}task.execute();}return nullptr;}pthread_t worker_thread;std::queue<AsyncTask> tasks;std::mutex mutex;std::condition_variable cond_var;bool stop;
};class MyApplication {
public:MyApplication(AsyncTaskProcessor &processor) : processor(processor) {}void run() {auto task = std::bind(&MyApplication::task_done_callback, this, std::placeholders::_1);processor.add_task(1, std::bind(task, "Task 1 completed"));processor.add_task(2, std::bind(task, "Task 2 completed"));}void task_done_callback(const std::string &message) {std::cout << message << std::endl;}private:AsyncTaskProcessor &processor;
};int main() {AsyncTaskProcessor processor;MyApplication app(processor);app.run();sleep(2); // Simulate runningreturn 0;
}

执行流程详细分析

  • 主函数启动逻辑
    • 创建processor和app对象,构造即可
    • 调用run启动程序,让主线程睡眠2秒,从而模拟程序运行,确保后续的线程完成任务

 

 创建和处理任务逻辑

  • 绑定task_done_callback方法,创建一个任务回调
  • 向processor添加两个任务,同时给task绑定一个字符串(也就是给回调函数绑定了对应的参数,只是封装了而已)

 

 任务处理线程

  • 创建一个空的 AsyncTask 对象 task
  • 锁定互斥量 self->mutex
  • 调用 self->cond_var.wait(lock, [self] { return !self->tasks.empty() || self->stop; })
    • 如果任务队列 tasks 为空且 stop 标志为 false,线程将等待条件变量 cond_var 的通知。
    • 一旦有新任务加入队列或 stop 标志为 true,条件满足,线程继续执行。
  • 检查 stop 标志和任务队列是否为空:
    • 如果 stoptrue 且任务队列为空,跳出循环,线程结束。
  • 将任务队列中的第一个任务移动到 task,并从任务队列中移除它
  • 解锁互斥量。
  • 调用 task.execute() 执行任务回调函数

 

最后执行任务回调函数(预先设置好的) ,执行处理器析构等待线程退出,避免僵尸线程

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • LeetCode高频
  • Charles怎么修改参数
  • 擅于辩论的人可以将黑的说成白的,但是存在无法解决的矛盾
  • 【已解决】ERROR: No matching distribution found for torch.安装torch一次性解决方法
  • 【Python】正色表达式 - 验证罗马数字
  • [VS Code扩展]写一个代码片段管理插件(一):介绍与界面搭建
  • jdk和tomcat的环境配置以及使用nginx代理tomcat来实现负载均衡
  • 0064__管道 (进程间通信)
  • C++数据结构学习(顺序表)
  • 洛谷 P1868 饥饿的奶牛
  • 实现一个全栈模糊搜索匹配的功能
  • 时空预测又爆火了!新SOTA实现零样本精准预测
  • C语言《智能自平衡小车,实现平衡功能的基础上,加入了超声波避障、超声波跟随、蓝牙遥控等功能》+源代码+文档说明
  • MySQL —— 初始数据库
  • 智能闹钟和普通闹钟有什么区别
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • android 一些 utils
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CODING 缺陷管理功能正式开始公测
  • isset在php5.6-和php7.0+的一些差异
  • Laravel 中的一个后期静态绑定
  • Meteor的表单提交:Form
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Rancher如何对接Ceph-RBD块存储
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • 包装类对象
  • 批量截取pdf文件
  • 前端面试总结(at, md)
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 我是如何设计 Upload 上传组件的
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 栈实现走出迷宫(C++)
  • 【云吞铺子】性能抖动剖析(二)
  • ​VRRP 虚拟路由冗余协议(华为)
  • ‌U盘闪一下就没了?‌如何有效恢复数据
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • $.ajax()方法详解
  • ()、[]、{}、(())、[[]]命令替换
  • (02)vite环境变量配置
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (C语言)字符分类函数
  • (SpringBoot)第七章:SpringBoot日志文件
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (数据结构)顺序表的定义
  • **CI中自动类加载的用法总结
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .mysql secret在哪_MySQL如何使用索引
  • .NET Core 中插件式开发实现
  • .NET MVC 验证码
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据