c++的回调函数

理论知识

一、回调监听函数的核心概念

1. 定义

回调监听函数(简称回调函数):是一种「先注册、后触发」的函数,指你将函数的地址(指针)传递给某个模块(如框架、第三方库、自定义逻辑),当特定事件(如异步操作完成、状态变更、定时器到期)发生时,该模块会主动调用这个函数来通知你(完成 "监听" 效果)。

2. 核心特性
  • 反向调用:不是你主动调用函数,而是被其他模块被动触发;
  • 解耦性:调用方和被调用方无需直接依赖,通过函数接口通信;
  • 灵活性:可动态注册不同的回调函数,适配不同业务逻辑。

二、C++ 回调函数的 4 种实现方式(按常用程度排序)

方式 1:函数指针(C 风格,最基础)

函数指针是 C++ 回调的基础,通过存储函数地址实现回调,适合简单场景(无状态、全局 / 静态函数)。

代码示例:事件监听回调
复制代码
#include <iostream>
#include <string>

// 1. 定义回调函数类型(简化函数指针声明)
typedef void (*EventCallback)(const std::string& eventName, int eventId);

// 2. 回调函数实现(符合回调类型签名)
void OnButtonClick(const std::string& eventName, int eventId) {
    std::cout << "监听到按钮点击事件:" << eventName << ",事件ID:" << eventId << std::endl;
}

void OnTimerTimeout(const std::string& eventName, int eventId) {
    std::cout << "监听到定时器超时事件:" << eventName << ",事件ID:" << eventId << std::endl;
}

// 3. 事件管理器(负责注册和触发回调)
class EventManager {
public:
    // 注册回调函数
    void registerCallback(EventCallback cb) {
        m_callback = cb;
    }

    // 模拟事件发生,触发回调
    void triggerEvent(const std::string& eventName, int eventId) {
        if (m_callback != nullptr) {
            m_callback(eventName, eventId); // 调用回调函数
        } else {
            std::cout << "未注册回调函数" << std::endl;
        }
    }

private:
    EventCallback m_callback = nullptr; // 存储回调函数地址
};

// 测试
int main() {
    EventManager manager;

    // 注册按钮点击回调
    manager.registerCallback(OnButtonClick);
    manager.triggerEvent("按钮点击", 1001);

    // 切换为定时器超时回调
    manager.registerCallback(OnTimerTimeout);
    manager.triggerEvent("定时器超时", 2001);

    return 0;
}
特点
  • 优点:简单高效、兼容性好(兼容 C 语言);
  • 缺点:仅支持全局 / 静态函数,无法捕获类成员变量(无状态),不支持灵活的参数绑定。
方式 2:类成员函数指针(面向对象场景)

在 C++ 类中,成员函数有隐含的 this 指针,无法直接用普通函数指针存储,需使用「类成员函数指针」实现回调,适合监听类内部事件。

代码示例:类成员函数回调
复制代码
#include <iostream>
#include <string>

// 事件监听类
class EventListener {
public:
    // 类成员回调函数
    void OnNetworkSuccess(const std::string& msg) {
        std::cout << "类内监听:网络请求成功,消息:" << msg << std::endl;
    }

    void OnNetworkFailed(const std::string& msg) {
        std::cout << "类内监听:网络请求失败,消息:" << msg << std::endl;
    }
};

// 网络管理器
class NetworkManager {
public:
    // 定义类成员函数指针类型(需指定类名)
    typedef void (EventListener::*NetworkCallback)(const std::string& msg);

    // 注册回调(需传递对象实例和成员函数指针)
    void registerCallback(EventListener* listener, NetworkCallback cb) {
        m_listener = listener;
        m_callback = cb;
    }

    // 模拟网络请求完成,触发回调
    void requestData(const std::string& url) {
        std::cout << "正在请求:" << url << std::endl;
        // 模拟请求成功
        if (m_listener != nullptr && m_callback != nullptr) {
            // 调用类成员函数指针(必须通过对象实例调用)
            (m_listener->*m_callback)("数据加载完成");
        }
    }

private:
    EventListener* m_listener = nullptr;
    NetworkCallback m_callback = nullptr;
};

// 测试
int main() {
    EventListener listener;
    NetworkManager netManager;

    // 注册类成员回调
    netManager.registerCallback(&listener, &EventListener::OnNetworkSuccess);
    netManager.requestData("https://example.com/data");

    // 切换为失败回调
    netManager.registerCallback(&listener, &EventListener::OnNetworkFailed);
    netManager.requestData("https://example.com/error");

    return 0;
}
特点
  • 优点:支持类成员函数,可访问类的成员变量(有状态);
  • 缺点:语法繁琐(需指定类名),只能绑定单个类的成员函数,灵活性不足。
方式 3:std::function + std::bind(C++11 及以上,推荐)

std::function 是 C++11 提供的通用函数包装器,可存储任意可调用对象(普通函数、成员函数、lambda 表达式等),配合 std::bind 可绑定类成员函数和参数,是最灵活的回调实现方式。

代码示例:灵活的回调绑定
复制代码
#include <iostream>
#include <string>
#include <functional> // 包含std::function和std::bind

// 回调类型定义(std::function包装,支持任意可调用对象)
using NotifyCallback = std::function<void(const std::string&, int)>;

// 消息通知器
class Notifier {
public:
    void registerCallback(NotifyCallback cb) {
        m_callback = std::move(cb); // 移动语义,提高效率
    }

    void sendNotify(const std::string& title, int priority) {
        std::cout << "准备发送通知..." << std::endl;
        if (m_callback) { // std::function可直接判断是否有效
            m_callback(title, priority); // 触发回调
        }
    }

private:
    NotifyCallback m_callback;
};

// 业务类(包含成员函数)
class BusinessService {
public:
    void onNotifyReceived(const std::string& title, int priority, const std::string& extra) {
        std::cout << "业务服务收到通知:" << title 
                  << ",优先级:" << priority 
                  << ",附加信息:" << extra << std::endl;
    }
};

// 测试
int main() {
    Notifier notifier;
    BusinessService business;

    // 1. 绑定普通函数
    auto normalFunc = [](const std::string& title, int priority) {
        std::cout << "普通Lambda回调:" << title << ",优先级:" << priority << std::endl;
    };
    notifier.registerCallback(normalFunc);
    notifier.sendNotify("系统公告", 1);

    // 2. 绑定类成员函数(用std::bind绑定this和额外参数)
    auto memberFunc = std::bind(&BusinessService::onNotifyReceived, &business,
                                 std::placeholders::_1, // 对应第一个参数(title)
                                 std::placeholders::_2, // 对应第二个参数(priority)
                                 "来自业务模块"); // 额外绑定的固定参数
    notifier.registerCallback(memberFunc);
    notifier.sendNotify("业务告警", 2);

    // 3. 直接绑定带捕获的Lambda(最简洁)
    std::string user = "张三";
    notifier.registerCallback([&user](const std::string& title, int priority) {
        std::cout << "用户" << user << "收到通知:" << title << ",优先级:" << priority << std::endl;
    });
    notifier.sendNotify("个人消息", 3);

    return 0;
}
特点
  • 优点:
    1. 支持任意可调用对象(普通函数、成员函数、lambda、函数对象);
    2. 可绑定额外参数,支持捕获 lambda 的上下文变量;
    3. 语法简洁,类型安全,是 C++ 现代开发的首选;
  • 缺点:需 C++11 及以上版本支持,少量性能开销(可忽略,满足绝大多数场景)。
方式 4:函数对象(仿函数,适用于复杂逻辑)

函数对象是重载了 operator() 的类 / 结构体,可存储状态(成员变量),适合回调逻辑复杂、需要复用状态的场景。

代码示例:函数对象回调
复制代码
#include <iostream>
#include <string>

// 函数对象(仿函数):日志回调器
class LogCallback {
public:
    // 构造函数:初始化日志级别
    LogCallback(const std::string& level) : m_logLevel(level) {}

    // 重载operator(),作为回调入口
    void operator()(const std::string& content) {
        std::cout << "[" << m_logLevel << "] " << content << std::endl;
    }

private:
    std::string m_logLevel; // 存储状态(日志级别)
};

// 日志管理器
class LogManager {
public:
    using LogFunc = LogCallback; // 函数对象类型

    void setLogCallback(LogFunc cb) {
        m_logCb = std::move(cb);
    }

    void log(const std::string& content) {
        m_logCb(content); // 调用函数对象
    }

private:
    LogFunc m_logCb;
};

// 测试
int main() {
    LogManager logManager;

    // 注册INFO级别日志回调
    logManager.setLogCallback(LogCallback("INFO"));
    logManager.log("程序启动成功");

    // 注册ERROR级别日志回调
    logManager.setLogCallback(LogCallback("ERROR"));
    logManager.log("文件读取失败");

    return 0;
}
特点
  • 优点:可存储状态(无需依赖外部变量),逻辑封装性好,适合复杂回调场景;
  • 缺点:语法比 lambda 繁琐,灵活性略低于 std::function

三、回调监听函数的典型使用场景

  1. 异步操作通知:如网络请求完成、文件读写结束、线程任务执行完毕后的结果回调;
  2. 事件监听:如 UI 按钮点击、定时器超时、状态变更(如数据更新)的事件响应;
  3. 框架扩展:如第三方库(如 OpenCV、Qt)的回调接口(如 Qt 的信号槽本质是回调的封装);
  4. 算法回调:如排序算法的自定义比较函数、遍历算法的元素处理回调。

四、使用回调函数的注意事项

  1. 生命周期管理:确保回调函数所依赖的对象(如类实例)在回调触发时未被销毁(避免野指针 / 悬空引用);
  2. 线程安全:若回调在多线程环境下触发,需保证回调函数内的操作线程安全(如加锁);
  3. 避免回调嵌套过深:过多回调嵌套会导致 "回调地狱",可通过 Promise/Future(C++11 及以上)优化;
  4. 类型匹配:回调函数的参数类型、返回值类型必须与注册接口的要求一致,否则会编译报错。

总结

  1. 回调监听函数是「先注册、后触发」的被动调用机制,核心作用是解耦和实现事件驱动;
  2. 实现方式优先级:std::function + lambda(C++11 + 首选)> 类成员函数指针 > 函数指针 > 函数对象;
  3. 关键技巧:std::bind 用于绑定类成员函数和固定参数,lambda 用于简洁捕获上下文,std::function 提供通用包装;
  4. 注意事项:重点关注对象生命周期和线程安全,避免悬空引用和数据竞争。

先搞懂核心类比:把回调函数比作「快递代收」

一、我们先通过生活场景理解「回调监听」的本质,对应代码里的角色:

生活场景(快递代收) C++ 代码中的角色
你(业主):需要收到快递到达的通知 回调函数(OnButtonClick/OnTimerTimeout):需要响应事件
快递柜(中间方):保管快递,到件后提醒你 事件管理器(EventManager):管理事件,触发回调
你把手机号留给快递柜(注册通知方式) 调用 registerCallback:把回调函数 "交给" 事件管理器
快递到了,快递柜给你发短信(触发通知) 调用 triggerEvent:事件发生,管理器调用回调函数
你收到短信后,去取快递(响应通知) 回调函数执行:打印事件信息(响应事件)

简单说:回调函数就是 "你留给别人的联系方式",别人(事件管理器)在特定时机(事件发生),会主动用这个 "联系方式"(调用回调函数)联系你(执行响应逻辑)

二、逐行白话解释代码(从顶到底,不跳步)

复制代码
#include <iostream>  // 引入"输出打印"的工具(类似生活中的打印机,用来显示文字)
#include <string>    // 引入"字符串"工具(用来存储文字内容,如事件名称)
1. 定义回调函数类型(相当于 "约定联系方式的格式")
复制代码
// 1. 定义回调函数类型(简化函数指针声明)
typedef void (*EventCallback)(const std::string& eventName, int eventId);

白话解释:

  • 这行是「约定规则」:告诉事件管理器,"能被你用来通知的函数(联系方式),必须长这样";
  • 规则细节:
    • void:这个函数执行完不需要返回任何结果(类似你收到快递短信后,不用给快递柜回消息);
    • (*EventCallback):给这个 "函数格式" 起个名字叫 EventCallback(相当于 "快递通知方式" 这个统称);
    • (const std::string& eventName, int eventId):这个函数必须接收两个参数(类似短信里必须包含 "快递类型" 和 "快递柜编号"):
      • 第一个参数:字符串类型(eventName),存放事件名称(如 "按钮点击");
      • 第二个参数:整数类型(eventId),存放事件 ID(如 1001,用来区分不同事件)。
2. 实现回调函数(相当于 "准备好你的联系方式对应的响应动作")
复制代码
// 2. 回调函数实现(符合回调类型签名)
void OnButtonClick(const std::string& eventName, int eventId) {
    // 当按钮点击事件发生时,执行这里的逻辑:打印事件信息(类似你收到快递短信后,念叨一句"快递到了")
    std::cout << "监听到按钮点击事件:" << eventName << ",事件ID:" << eventId << std::endl;
}

void OnTimerTimeout(const std::string& eventName, int eventId) {
    // 当定时器超时事件发生时,执行这里的逻辑:打印事件信息
    std::cout << "监听到定时器超时事件:" << eventName << ",事件ID:" << eventId << std::endl;
}

白话解释:

  • 这两个函数就是 "具体的联系方式对应的动作",它们完全符合上面约定的 "函数格式"(返回 void,接收两个参数);
  • OnButtonClick:专门响应 "按钮点击" 事件(类似 "收到顺丰快递短信,就去小区东门快递柜取");
  • OnTimerTimeout:专门响应 "定时器超时" 事件(类似 "收到京东快递短信,就去小区西门快递柜取")。
3. 事件管理器(相当于 "快递柜,负责管理通知")
复制代码
// 3. 事件管理器(负责注册和触发回调)
class EventManager {
public:
    // 注册回调函数(相当于"你把手机号留给快递柜,快递柜存起来")
    void registerCallback(EventCallback cb) {
        m_callback = cb;  // 把传入的回调函数(联系方式),存到管理器的内部变量里
    }

    // 模拟事件发生,触发回调(相当于"快递到了,快递柜给你发短信")
    void triggerEvent(const std::string& eventName, int eventId) {
        // 先判断:有没有存过回调函数(联系方式)
        if (m_callback != nullptr) {
            // 如果存了,就调用这个回调函数,把事件名称和ID传进去(相当于发短信,把快递信息发给你)
            m_callback(eventName, eventId);
        } else {
            // 如果没存,就打印"没人留联系方式,没法通知"
            std::cout << "未注册回调函数" << std::endl;
        }
    }

private:
    // 用来存储回调函数(联系方式)的变量,初始值是nullptr(相当于快递柜一开始没存任何手机号)
    EventCallback m_callback = nullptr;
};

白话解释:

  • EventManager 是一个 "事件管理类",就像快递柜,只有两个核心功能:
    1. registerCallback(存联系方式):接收外部传进来的回调函数,然后存起来;
    2. triggerEvent(发通知):当事件发生时,检查有没有存过回调函数,有就调用(发通知),没有就提示。
4. 测试代码(相当于 "实际演示:你存联系方式→快递到→收到通知")
复制代码
// 测试
int main() {
    EventManager manager;  // 创建一个事件管理器(相当于买了一个快递柜放在小区里)

    // 注册按钮点击回调(相当于你把"顺丰快递通知方式"留给快递柜)
    manager.registerCallback(OnButtonClick);
    // 触发按钮点击事件(相当于顺丰快递到了,快递柜给你发通知)
    manager.triggerEvent("按钮点击", 1001);

    // 切换为定时器超时回调(相当于你更新联系方式,改成"京东快递通知方式")
    manager.registerCallback(OnTimerTimeout);
    // 触发定时器超时事件(相当于京东快递到了,快递柜给你发通知)
    manager.triggerEvent("定时器超时", 2001);

    return 0;  // 程序正常结束
}

代码运行结果(相当于 "实际发生的事情")

运行这段代码后,控制台会输出:

复制代码
监听到按钮点击事件:按钮点击,事件ID:1001
监听到定时器超时事件:定时器超时,事件ID:2001

对应生活场景:

  1. 快递柜收到顺丰快递,给你发了短信,你念叨 "顺丰快递到了,编号 1001";
  2. 你更新了快递柜的联系方式,之后快递柜收到京东快递,给你发了短信,你念叨 "京东快递到了,编号 2001"。

三、核心总结(一句话概括)

这段代码的本质就是:先约定 "响应函数的格式",再写 "具体的响应函数",然后把 "响应函数" 交给 "事件管理器",最后 "事件管理器" 在特定时机主动调用 "响应函数",完成 "事件发生→响应" 的流程(也就是回调监听的核心逻辑)。

相关推荐
一棵开花的树,枝芽无限靠近你2 小时前
【face-api.js】2️⃣ NetInput - 神经网络输入封装类
开发语言·javascript·神经网络
yongche_shi2 小时前
第九十九篇:Python在其他领域的应用:游戏开发、物联网、AIoT简介
开发语言·python·物联网·游戏开发·aiot
froginwe112 小时前
Node.js 回调函数
开发语言
期待のcode2 小时前
Java中的继承
java·开发语言
资深低代码开发平台专家2 小时前
MicroQuickJS:为极致资源而生的嵌入式JavaScript革命
开发语言·javascript·ecmascript
世转神风-2 小时前
qt-通信协议基础-固定长度-小端字节序补0x00指导
开发语言·qt
期待のcode2 小时前
Java中的super关键字
java·开发语言
TM1Club2 小时前
Zoey的TM1聊天室|#3 合并报表提速:业财一体如何实现关联方对账自动化
大数据·开发语言·人工智能·经验分享·数据分析·自动化·数据库系统
Selegant2 小时前
百万 QPS 下的 Java 服务调优:JVM 参数、GC 策略与异步非阻塞编程
java·开发语言·jvm