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

news2024/11/15 8:21:15

 回调函数基本知识回顾

回调函数是什么

  • 函数指针或者函数对象作为参数传递给另一个函数的机制,当某个事件发生的时候,系统会自动的调用这些函数进行处理
  • 事件驱动模型中作用,回调函数则被用于处理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: 10

    return 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: 20

    return 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: 42

    return 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 绑定普通函数,固定第一个参数为 10
    auto bound_print = std::bind(print, 10, std::placeholders::_1);
    
    // 使用 std::function 存储绑定后的函数
    std::function<void(int)> func = bound_print;
    func(20); // 输出: a: 10, b: 20

    return 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,并固定第一个参数为 10
    auto 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: 20

    return 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 running
    return 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 running
    return 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 running
    return 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() 执行任务回调函数

 

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

 

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

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

相关文章

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备 ​ 大家好&#xff0c;因为板端BPU环境&#xff0c;可以加速目标检测的速度&#xff0c;所以今天在此先给大家带来如何准备一个模型&#xff0c;下一期会给大家带来如何在板端部…

如何做一个惊艳领导和客户的原型?

在产品开发过程中&#xff0c;原型设计是验证设计想法、提升用户体验的重要环节。Axure作为一款业界领先的原型设计工具&#xff0c;凭借其强大的交互设计和丰富的功能&#xff0c;赢得了全球设计师和开发者的信赖。而Axure的高效交互元件库&#xff0c;则如同一本字典或说明书…

将YOLOv8模型从PyTorch的.pt格式转换为OpenVINO支持的IR格式

OpenVINO是Open Visual Inference & Neural Network Optimization工具包的缩写&#xff0c;是一个用于优化和部署AI推理模型的综合工具包。OpenVINO支持CPU、GPU和NPU设备。 OpenVINO的优势: (1).性能&#xff1a;OpenVINO利用英特尔CPU、集成和独立GPU以及FPGA的强大功能提…

原生IP节点是什么意思?和socks5节点有什么区别?

在了解这两种代理节点前&#xff0c;我们首先要了解&#xff1a;节点是什么&#xff1f; 首先&#xff0c;在电信网络当中&#xff0c;一个节点是一个连接点。表示一个再分发点又或者是一个通信端点。节点的定义依赖于所提及的网络和协议层。一个物理网络节点是一个连接到网络…

深度强化学习:穿越智能迷雾,探索AI新纪元

近年来&#xff0c;深度强化学习成为关注的热点。在自动驾驶、棋牌游戏、分子重排和机器人等领域&#xff0c;计算机程序能够通过强化学习&#xff0c;理解以前被视为超级困难的问题&#xff0c;取得了令人瞩目的成果。在围棋比赛中&#xff0c;AlphaGo接连战胜樊麾、李世石和柯…

使用 SpringBoot + 虚拟线程将服务性能提升几百倍

虚拟线程简介 虚拟线程是 Java 平台的一项创新特性。虚拟线程是一种轻量级的线程实现,它在操作系统层面并不对应真实的内核线程,而是由 JVM 进行管理和调度。这使得可以在不消耗大量系统资源的情况下创建大量的线程,从而能够更高效地处理并发任务。 虚拟线程与普通线程的区…

【数学建模】——【A题 信用风险识别问题】全面解析

目录 1.题目 2.解答分析 问题1&#xff1a;指标筛选 1.1 问题背景 1.2 数据预处理 1.3 特征选择方法 1.4 多重共线性检测 1.5 实现步骤 问题2&#xff1a;信用评分模型 2.1 问题背景 2.2 数据分割 2.3 处理不平衡数据 2.4 模型选择与理由 问题3&#xff1a;模型对…

『 Linux 』线程池与 POSIX 线程的封装编码实现

文章目录 线程池概念线程池的编码实现线程池的测试参考代码 线程的封装使用测试封装后的线程参考代码 线程池概念 池化技术是一种资源管理方法,通过预先创建和管理一组资源以便在需要使用时快速分配这些资源; 线程池是池化技术的一种典型应用; 资源分配 在线程池中预先创建一定…

【python015】常见成熟AI-图像识别场景算法清单(已更新)

1.欢迎点赞、关注、批评、指正,互三走起来,小手动起来! 【python015】常见成熟AI-图像识别场景算法清单及代码【python015】常见成熟AI-图像识别场景算法清单及代码【python015】常见成熟AI-图像识别场景算法清单及代码文章目录 1.背景介绍2.`Python`版数据爬取、解析代码2.…

鸿蒙应用框架开发【画中画效果实现】 UI框架

画中画效果实现 介绍 本示例通过kit.ArkUI、kit.MediaKit等接口&#xff0c;实现了视频播放、手动和自动拉起画中画、画中画窗口控制视频播放和暂停等功能。 效果预览 使用说明 在主界面&#xff0c;可以点击对应视频按钮进入视频播放页面&#xff1b;视频播放页面点击开启…

三星One UI 7.0引入苹果的几大特色功能,iOS用户都羡慕哭了

在智能手机操作系统的创新之路上&#xff0c;苹果iOS系统的Dynamic Island和Live Activities功能无疑为用户带来了全新的交互体验。 现在&#xff0c;三星One UI 7.0系列的即将发布&#xff0c;似乎预示着安卓阵营也将迎头赶上&#xff0c;甚至可能在某些方面超越苹果。以下是…

AcWing-AcWing 837. 连通块中点的数量

在原来并查集的基础上增加一个Size数组&#xff0c;Size的初始化是必须先每个元素初始化为1 Size只对根节点有效&#xff0c;比如Size[find(1)]就是找1的祖先节点&#xff0c;然后访问祖先节点的个数。当我们联通a点和b点时&#xff0c;如果已经是联通状态了&#xff0c;那么无…

深度解读:等保测评标准与实践指南

在信息时代&#xff0c;数据安全与隐私保护成为企业和组织不可忽视的关键议题。等保测评&#xff0c;即信息安全等级保护测评&#xff0c;作为我国信息安全管理体系的重要组成部分&#xff0c;为各行业提供了标准化的安全评估与改进路径。本文旨在深度解读等保测评标准的核心要…

探索Python日期时间的宝藏:`dateutil`库的神秘面纱

文章目录 探索Python日期时间的宝藏&#xff1a;dateutil库的神秘面纱背景&#xff1a;为何选择dateutil&#xff1f;dateutil是什么&#xff1f;如何安装dateutil&#xff1f;简单函数介绍与使用parse函数&#xff1a;智能日期时间解析relativedelta&#xff1a;计算相对日期t…

一个超强的Python机器学习超参优化库

在机器学习模型的训练过程中,选择合适的超参数对模型性能的提升至关重要。超参数优化是指在给定的超参数空间内,找到一组能够使模型表现最佳的超参数组合。虽然有许多方法可以用来进行超参数优化,但在本文中,我们将重点介绍一个强大且易用的库——Optuna。 什么是Optuna?…

顺序表、单链表、顺序栈,链栈的基本运算

目录 顺序表的基本运算 单链表的基本运算 顺序栈的基本运算 链栈的基本运算 线性表的9个基本运算&#xff1a; 栈的6个基本运算&#xff1a; 顺序表的基本运算 //顺序表的基本运算************************************************************** #include<stdio…

通过yfinance获取股票历史数据

以比亚迪为例&#xff0c;要获取A股比亚迪的十年的历史数据并保存为CSV文件&#xff0c;我们可以使用Python中的第三方库如pandas和yfinance。yfinance库是一个用于下载雅虎财经数据的工具&#xff0c;它支持股票、期权等金融工具的数据获取。 1.安装yfinance和pandas 首先&a…

云服务器带宽什么意思?如何正确选择

云服务器带宽什么意思?云服务器带宽指的是云服务器在互联网上的数据传输能力。就像水流通过水管一样&#xff0c;数据通过所谓的“带宽”在网络中流动。这个带宽越大&#xff0c;每秒能够传输的数据就越多&#xff0c;对大量访问处理的能力也就越强。云服务器带宽是云服务器可…

【开源项目】基于RTP协议的H264码流发送器和接收器

RTP协议 1. 概述1.1 RTP协议1.2 RTP和UDP的关系 2. RTP打包H264码流2.1 RTP单一传输2.2 RTP分片传输2.3 RTP多片合入传输 3.工程3.1 头文件3.1.1 rtp.h3.1.2 utils.h 3.2 cpp文件3.2.1 rtp.cpp3.2.2 utils.cpp 4.测试5.小结 参考&#xff1a; 视音频数据处理入门&#xff1a;UD…

Arco Design 之Table表格

此篇文章为table表格示例&#xff0c;包含列、data数据、展开、选中、自定义等相关属性 基础表格 <a-table :columns"columns1" :data"tableData1" />const columns1 [{ title: "编号", dataIndex: "no"},{ title: "名称…