eventpp 全面教程(从入门到实战)
eventpp 是一款轻量级、类型安全、无依赖的纯 C++11 及以上事件分发/回调管理库(事件总线),核心价值是简化 C++ 项目的模块解耦与回调管理,仅专注于"事件-回调"的绑定、分发与生命周期管理,不具备事件循环和 IO 处理能力。本教程将从基础入门、核心用法、高级特性到实战场景,全方位讲解 eventpp 的使用。
一、教程前置准备
1. 环境要求
- 编译器:支持 C++11 及以上标准(GCC 4.8+、Clang 3.3+、MSVC 2013+)
- 无外部依赖:无需安装第三方库,仅需头文件/源码集成
- 跨平台:支持 Linux、macOS、Windows 等主流操作系统
2. 源码集成
eventpp 是头文件/源码级库,无需编译安装,集成步骤如下:
- 下载源码:从 GitHub eventpp 仓库 下载最新源码
- 项目集成:将源码中的
eventpp/include/eventpp/目录复制到项目中,在代码中通过#include <eventpp/xxx.h>引入对应头文件 - 编译配置:编译时指定 C++11 及以上标准(如 GCC 编译添加
-std=c++11参数)
二、核心概念(入门基础)
在使用 eventpp 前,必须掌握 3 个核心概念,它们是理解 eventpp 设计的基础:
| 概念 | 含义与作用 |
|---|---|
| EventBus(事件总线) | 核心管理容器,负责存储"事件类型-回调函数"的映射关系,提供注册、注销、触发事件的接口,支持单例/多例使用 |
| Event(事件) | 触发回调的"信号标识",可以是整数、枚举、字符串等任意可哈希类型(推荐枚举/强类型,保证类型安全) |
| Callback(回调) | 事件触发时执行的业务逻辑,支持普通函数、Lambda 表达式、类成员函数、函数对象,支持带参/无参、有返回值/无返回值 |
三、基础入门:核心API使用
1. 头文件引入
eventpp 核心功能通过 eventbus.h 提供,基础使用只需引入:
cpp
#include <eventpp/eventbus.h>
2. 第一步:定义事件类型
推荐使用枚举(enum/class)作为事件类型,保证类型安全,避免字符串/整数的硬编码错误:
cpp
// 普通枚举(兼容旧版C++)
enum BasicEvent {
Event_Init, // 初始化事件
Event_Quit // 退出事件
};
// 强类型枚举(推荐,避免枚举值冲突)
enum class BusinessEvent {
UserRegister, // 用户注册事件
OrderPay // 订单支付事件
};
// 字符串类型事件(灵活,适合动态事件场景)
using StringEvent = std::string;
3. 第二步:创建事件总线
事件总线是模板类,模板参数说明:
- 第一个参数:事件类型(如枚举、字符串)
- 第二个参数:回调函数签名(格式:
返回值类型(参数类型1, 参数类型2, ...),无参无返回值为void ())
cpp
// 示例1:无参无返回值的事件总线(对应 BasicEvent 枚举)
eventpp::EventBus<BasicEvent, void ()> basicEventBus;
// 示例2:带参数的事件总线(对应 BusinessEvent 枚举,回调接收2个参数)
eventpp::EventBus<BusinessEvent, void (int, std::string)> businessEventBus;
// 示例3:字符串事件类型 + 带返回值的事件总线
eventpp::EventBus<StringEvent, int (std::string)> stringEventBus;
4. 第三步:注册事件与回调
eventpp 提供 appendListener 接口注册回调,支持多种回调类型,核心用法如下:
(1)注册普通函数回调
cpp
// 普通函数(匹配 BasicEvent 事件总线的回调签名:void ())
void onEventInit() {
std::cout << "普通函数:初始化事件触发" << std::endl;
}
void onEventQuit() {
std::cout << "普通函数:退出事件触发" << std::endl;
}
// 注册普通函数
basicEventBus.appendListener(BasicEvent::Event_Init, onEventInit);
basicEventBus.appendListener(BasicEvent::Event_Quit, onEventQuit);
(2)注册 Lambda 表达式回调
Lambda 表达式支持捕获外部变量,是最灵活的回调方式:
cpp
// 无捕获 Lambda(匹配 BasicEvent 回调签名)
basicEventBus.appendListener(BasicEvent::Event_Init, []() {
std::cout << "Lambda 无捕获:初始化事件触发" << std::endl;
});
// 有捕获 Lambda(匹配 BusinessEvent 回调签名:void (int, std::string))
std::string tip = "业务通知:";
businessEventBus.appendListener(BusinessEvent::UserRegister, [&tip](int userId, std::string userName) {
std::cout << tip << "用户注册:ID=" << userId << ",用户名=" << userName << std::endl;
});
(3)注册类成员函数回调
类成员函数需要通过 std::bind 绑定类实例,才能注册到事件总线:
cpp
class OrderService {
public:
// 类成员函数(匹配 BusinessEvent 回调签名)
void onOrderPay(int orderId, std::string orderNo) {
std::cout << "【OrderService】订单支付:ID=" << orderId << ",订单号=" << orderNo << std::endl;
}
};
// 实例化类
OrderService orderService;
// 注册类成员函数(std::bind 绑定实例地址和函数地址)
businessEventBus.appendListener(BusinessEvent::OrderPay,
std::bind(&OrderService::onOrderPay, &orderService, std::placeholders::_1, std::placeholders::_2)
);
(4)同一事件注册多个回调
eventpp 支持给同一个事件注册多个回调,触发事件时会串行执行所有回调:
cpp
// 给 Event_Init 注册3个回调
basicEventBus.appendListener(BasicEvent::Event_Init, onEventInit);
basicEventBus.appendListener(BasicEvent::Event_Init, []() { std::cout << "Lambda 回调1" << std::endl; });
basicEventBus.appendListener(BasicEvent::Event_Init, []() { std::cout << "Lambda 回调2" << std::endl; });
5. 第四步:触发(分发)事件
通过 dispatch 接口触发事件,传递的参数需与回调签名严格匹配:
cpp
// 示例1:触发无参事件
std::cout << "--- 触发初始化事件 ---" << std::endl;
basicEventBus.dispatch(BasicEvent::Event_Init);
std::cout << "\n--- 触发退出事件 ---" << std::endl;
basicEventBus.dispatch(BasicEvent::Event_Quit);
// 示例2:触发带参数事件
std::cout << "\n--- 触发用户注册事件 ---" << std::endl;
businessEventBus.dispatch(BusinessEvent::UserRegister, 1001, "zhangsan");
std::cout << "\n--- 触发订单支付事件 ---" << std::endl;
businessEventBus.dispatch(BusinessEvent::OrderPay, 2001, "ORDER_20251222_001");
6. 第五步:注销事件回调
eventpp 支持动态注销回调,避免无效回调执行(如对象销毁后回调悬空),核心接口有两个:
removeListener:通过回调句柄注销单个回调(需保存注册时的返回值)removeAllListeners:批量注销某个事件的所有回调
(1)注销单个回调(通过句柄)
cpp
// 注册回调时保存句柄
const auto handle1 = basicEventBus.appendListener(BasicEvent::Event_Init, onEventInit);
const auto handle2 = basicEventBus.appendListener(BasicEvent::Event_Init, []() {
std::cout << "可注销的Lambda回调" << std::endl;
});
// 注销指定句柄的回调
basicEventBus.removeListener(BasicEvent::Event_Init, handle1);
std::cout << "--- 注销handle1后触发初始化事件 ---" << std::endl;
basicEventBus.dispatch(BasicEvent::Event_Init);
(2)批量注销所有回调
cpp
// 注销 Event_Init 的所有回调
basicEventBus.removeAllListeners(BasicEvent::Event_Init);
std::cout << "\n--- 批量注销后触发初始化事件 ---" << std::endl;
basicEventBus.dispatch(BasicEvent::Event_Init); // 无任何回调执行
四、高级特性
1. 线程安全的事件总线
默认的 eventpp::EventBus 非线程安全,多线程环境(如主线程注册回调、工作线程触发事件)中会存在数据竞争,此时需使用 eventpp::ConcurrentEventBus(线程安全版本,内部通过互斥锁保证线程安全)。
用法示例
cpp
#include <iostream>
#include <thread>
#include <eventpp/concurrenteventbus.h>
enum class ThreadEvent {
DoTask
};
void workerThread(eventpp::ConcurrentEventBus<ThreadEvent, void (int)> *bus) {
// 工作线程触发事件
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
bus->dispatch(ThreadEvent::DoTask, i);
}
}
int main() {
// 创建线程安全事件总线
eventpp::ConcurrentEventBus<ThreadEvent, void (int)> concurrentBus;
// 主线程注册回调
concurrentBus.appendListener(ThreadEvent::DoTask, [](int taskId) {
std::cout << "主线程接收任务:ID=" << taskId << std::endl;
});
// 创建工作线程
std::thread t(workerThread, &concurrentBus);
// 等待工作线程结束
t.join();
return 0;
}
2. 带返回值的回调处理
eventpp 支持回调返回值,dispatch 会返回一个包含所有回调返回值的 std::vector,可用于汇总多个回调的执行结果。
用法示例
cpp
#include <iostream>
#include <vector>
#include <eventpp/eventbus.h>
enum class CalcEvent {
Sum
};
// 带返回值的回调函数
int calc1(int a, int b) {
return a + b;
}
int calc2(int a, int b) {
return (a + b) * 2;
}
int main() {
// 创建带返回值的事件总线(回调签名:int (int, int))
eventpp::EventBus<CalcEvent, int (int, int)> calcBus;
// 注册两个带返回值的回调
calcBus.appendListener(CalcEvent::Sum, calc1);
calcBus.appendListener(CalcEvent::Sum, calc2);
// 触发事件,接收所有回调的返回值
std::vector<int> results = calcBus.dispatch(CalcEvent::Sum, 10, 20);
// 遍历返回值
std::cout << "回调返回值汇总:" << std::endl;
for (size_t i = 0; i < results.size(); ++i) {
std::cout << "回调" << (i+1) << "返回值:" << results[i] << std::endl;
}
return 0;
}
运行结果
回调返回值汇总:
回调1返回值:30
回调2返回值:60
3. 事件过滤器(拦截事件)
eventpp 支持通过 setFilter 设置事件过滤器,在回调执行前拦截事件,可实现"事件过滤""参数修改"等功能,过滤器返回 true 表示继续触发回调,返回 false 表示拦截事件(不执行后续回调)。
用法示例
cpp
#include <iostream>
#include <eventpp/eventbus.h>
enum class FilterEvent {
UserLogin
};
int main() {
eventpp::EventBus<FilterEvent, void (int, std::string)> filterBus;
// 注册回调
filterBus.appendListener(FilterEvent::UserLogin, [](int userId, std::string userName) {
std::cout << "用户登录:ID=" << userId << ",用户名=" << userName << std::endl;
});
// 设置事件过滤器(拦截非法用户ID(<1000))
filterBus.setFilter([](const FilterEvent &event, int userId, std::string &userName) -> bool {
if (event != FilterEvent::UserLogin) {
return true; // 非登录事件不拦截
}
if (userId < 1000) {
std::cout << "过滤器拦截:非法用户ID=" << userId << std::endl;
return false; // 拦截事件,不执行回调
}
// 可修改参数
userName += "_valid";
return true; // 放行事件,执行回调
});
// 触发非法用户事件(被拦截)
filterBus.dispatch(FilterEvent::UserLogin, 999, "wangwu");
// 触发合法用户事件(放行,参数被修改)
filterBus.dispatch(FilterEvent::UserLogin, 1001, "zhangsan");
return 0;
}
运行结果
过滤器拦截:非法用户ID=999
用户登录:ID=1001,用户名=zhangsan_valid
4. 全局单例事件总线(跨模块通信)
实际项目中,多模块间的事件通信通常使用全局单例事件总线,避免事件总线对象的传递,实现模块解耦。
实现示例
cpp
#include <iostream>
#include <string>
#include <eventpp/eventbus.h>
// 全局事件总线单例类
class GlobalEventBus {
public:
// 获取单例实例(线程安全,C++11静态局部变量保证初始化原子性)
static GlobalEventBus &getInstance() {
static GlobalEventBus instance;
return instance;
}
// 注册回调(封装 eventpp 接口)
template <typename EventType, typename Callback>
auto appendListener(const EventType &event, const Callback &callback) {
return eventBus.appendListener(event, callback);
}
// 触发事件(封装 eventpp 接口)
template <typename EventType, typename... Args>
auto dispatch(const EventType &event, Args&&... args) {
return eventBus.dispatch(event, std::forward<Args>(args)...);
}
// 注销单个回调
template <typename EventType, typename Handle>
void removeListener(const EventType &event, const Handle &handle) {
eventBus.removeListener(event, handle);
}
// 批量注销回调
template <typename EventType>
void removeAllListeners(const EventType &event) {
eventBus.removeAllListeners(event);
}
// 禁止拷贝与赋值(保证单例唯一性)
GlobalEventBus(const GlobalEventBus &) = delete;
GlobalEventBus &operator=(const GlobalEventBus &) = delete;
private:
// 私有构造函数(禁止外部实例化)
GlobalEventBus() = default;
// 内部事件总线对象(可根据需求改为 ConcurrentEventBus)
eventpp::EventBus<std::string, void (std::string)> eventBus;
};
// 模块A:发送事件
class ModuleA {
public:
void sendMessage(const std::string &msg) {
std::cout << "【ModuleA】发送消息事件:" << msg << std::endl;
// 触发全局事件
GlobalEventBus::getInstance().dispatch("MessageEvent", msg);
}
};
// 模块B:接收事件
class ModuleB {
public:
ModuleB() {
// 注册全局事件回调
GlobalEventBus::getInstance().appendListener("MessageEvent", [this](const std::string &msg) {
this->onMessageReceived(msg);
});
}
private:
void onMessageReceived(const std::string &msg) {
std::cout << "【ModuleB】接收消息:" << msg << std::endl;
}
};
// 模块C:接收事件
class ModuleC {
public:
ModuleC() {
// 注册全局事件回调
GlobalEventBus::getInstance().appendListener("MessageEvent", [](const std::string &msg) {
std::cout << "【ModuleC】接收消息:" << msg << std::endl;
});
}
};
int main() {
// 初始化模块(自动注册事件回调)
ModuleB moduleB;
ModuleC moduleC;
ModuleA moduleA;
// 模块间通信
moduleA.sendMessage("Hello, Global EventBus!");
moduleA.sendMessage("实现跨模块解耦!");
return 0;
}
运行结果
【ModuleA】发送消息事件:Hello, Global EventBus!
【ModuleB】接收消息:Hello, Global EventBus!
【ModuleC】接收消息:Hello, Global EventBus!
【ModuleA】发送消息事件:实现跨模块解耦!
【ModuleB】接收消息:实现跨模块解耦!
【ModuleC】接收消息:实现跨模块解耦!
五、实战场景:耗时回调异步处理
在实际项目中,若回调包含耗时操作(如 IO、复杂计算),直接同步执行会阻塞事件触发线程,核心解决方案是借助线程池将耗时回调异步化,具体实现如下(复用简易线程池):
1. 完整实战代码
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <eventpp/concurrenteventbus.h>
// 1. 简易线程池实现
class SimpleThreadPool {
public:
explicit SimpleThreadPool(size_t threadNum = std::thread::hardware_concurrency()) {
for (size_t i = 0; i < threadNum; ++i) {
threads_.emplace_back([this]() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mtx_);
cv_.wait(lock, [this]() { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
~SimpleThreadPool() {
{
std::unique_lock<std::mutex> lock(mtx_);
stop_ = true;
}
cv_.notify_all();
for (auto &t : threads_) {
if (t.joinable()) t.join();
}
}
template <typename F, typename... Args>
void submit(F &&f, Args&&... args) {
auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
{
std::unique_lock<std::mutex> lock(mtx_);
if (stop_) throw std::runtime_error("线程池已停止");
tasks_.emplace(std::move(task));
}
cv_.notify_one();
}
SimpleThreadPool(const SimpleThreadPool &) = delete;
SimpleThreadPool &operator=(const SimpleThreadPool &) = delete;
private:
std::vector<std::thread> threads_;
std::queue<std::function<void()>> tasks_;
std::mutex mtx_;
std::condition_variable cv_;
bool stop_ = false;
};
// 2. 定义事件类型
enum class BusinessEvent {
DoHeavyTask, // 触发耗时任务
TaskCompleted // 任务完成通知
};
int main() {
// 初始化线程池(4个工作线程)
SimpleThreadPool threadPool(4);
// 初始化线程安全事件总线
eventpp::ConcurrentEventBus<BusinessEvent, void (int)> eventBus;
eventpp::ConcurrentEventBus<BusinessEvent, void (int, std::string)> completeBus;
// 3. 注册耗时任务回调(异步提交到线程池)
eventBus.appendListener(BusinessEvent::DoHeavyTask, [&](int taskId) {
std::cout << "【主线程】提交耗时任务,ID:" << taskId << std::endl;
// 耗时逻辑提交到线程池
threadPool.submit([=, &completeBus]() {
// 模拟耗时操作(2秒)
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "【工作线程】耗时任务完成,ID:" << taskId << std::endl;
// 触发任务完成事件
completeBus.dispatch(BusinessEvent::TaskCompleted, taskId, "执行成功");
});
});
// 4. 注册任务完成回调(轻量同步执行)
completeBus.appendListener(BusinessEvent::TaskCompleted, [](int taskId, std::string status) {
std::cout << "【通知】任务 " << taskId << " 状态:" << status << std::endl;
});
// 5. 批量触发耗时任务(验证主线程不阻塞)
for (int i = 1; i <= 3; ++i) {
eventBus.dispatch(BusinessEvent::DoHeavyTask, i);
std::cout << "【主线程】继续执行其他逻辑" << std::endl;
}
// 等待所有任务完成
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "=== 所有任务处理完毕 ===" << std::endl;
return 0;
}
2. 运行结果(主线程无阻塞)
【主线程】提交耗时任务,ID:1
【主线程】继续执行其他逻辑
【主线程】提交耗时任务,ID:2
【主线程】继续执行其他逻辑
【主线程】提交耗时任务,ID:3
【主线程】继续执行其他逻辑
【工作线程】耗时任务完成,ID:1
【通知】任务 1 状态:执行成功
【工作线程】耗时任务完成,ID:2
【通知】任务 2 状态:执行成功
【工作线程】耗时任务完成,ID:3
【通知】任务 3 状态:执行成功
=== 所有任务处理完毕 ===
六、使用注意事项
- 回调生命周期管理 :若回调是类成员函数,需确保类实例生命周期长于事件触发周期,可使用
std::shared_ptr/std::weak_ptr避免悬空指针 - 参数传递细节 :回调参数优先使用
const &(避免大对象拷贝),需修改参数时使用普通引用 - 避免循环事件:防止"A事件触发B事件,B事件又触发A事件"导致的无限循环
- 线程安全选择 :单线程场景用
EventBus(效率更高),多线程场景用ConcurrentEventBus(线程安全) - 过度异步警告:轻量回调(如状态更新、简单日志)无需异步,同步执行效率更高,仅对耗时操作异步化
七、总结
- eventpp 是轻量级事件总线库,核心价值是模块解耦与回调管理,无IO/事件循环能力,需搭配其他框架处理异步IO
- 核心流程:定义事件类型 → 创建事件总线 → 注册回调 → 触发事件 → (可选)注销回调
- 高级特性:线程安全总线、带返回值回调、事件过滤器、全局单例总线,满足复杂业务场景
- 耗时回调解决方案:借助线程池异步执行,避免阻塞事件触发线程
- 适用场景:C++ 项目模块通信、GUI 事件分发、回调管理优化、无IO依赖的事件驱动场景