转载自:https://blog.csdn.net/weixin_73527660/article/details/156298635
作者:我的offer在哪里
理论知识
一、回调监听函数的核心概念
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;
}
特点
- 优点:
- 支持任意可调用对象(普通函数、成员函数、lambda、函数对象);
- 可绑定额外参数,支持捕获 lambda 的上下文变量;
- 语法简洁,类型安全,是 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。
三、回调监听函数的典型使用场景
- 异步操作通知:如网络请求完成、文件读写结束、线程任务执行完毕后的结果回调;
- 事件监听:如 UI 按钮点击、定时器超时、状态变更(如数据更新)的事件响应;
- 框架扩展:如第三方库(如 OpenCV、Qt)的回调接口(如 Qt 的信号槽本质是回调的封装);
- 算法回调:如排序算法的自定义比较函数、遍历算法的元素处理回调。
四、使用回调函数的注意事项
- 生命周期管理:确保回调函数所依赖的对象(如类实例)在回调触发时未被销毁(避免野指针 / 悬空引用);
- 线程安全:若回调在多线程环境下触发,需保证回调函数内的操作线程安全(如加锁);
- 避免回调嵌套过深:过多回调嵌套会导致 “回调地狱”,可通过 Promise/Future(C++11 及以上)优化;
- 类型匹配:回调函数的参数类型、返回值类型必须与注册接口的要求一致,否则会编译报错。
五、总结
- 回调监听函数是「先注册、后触发」的被动调用机制,核心作用是解耦和实现事件驱动;
- 实现方式优先级:
std::function + lambda(C++11 + 首选)> 类成员函数指针 > 函数指针 > 函数对象; - 关键技巧:
std::bind用于绑定类成员函数和固定参数,lambda 用于简洁捕获上下文,std::function提供通用包装; - 注意事项:重点关注对象生命周期和线程安全,避免悬空引用和数据竞争。
850

被折叠的 条评论
为什么被折叠?



