eventpp 全面教程(从入门到实战)

eventpp 全面教程(从入门到实战)

eventpp 是一款轻量级、类型安全、无依赖的纯 C++11 及以上事件分发/回调管理库(事件总线),核心价值是简化 C++ 项目的模块解耦与回调管理,仅专注于"事件-回调"的绑定、分发与生命周期管理,不具备事件循环和 IO 处理能力。本教程将从基础入门、核心用法、高级特性到实战场景,全方位讲解 eventpp 的使用。

一、教程前置准备

1. 环境要求

  • 编译器:支持 C++11 及以上标准(GCC 4.8+、Clang 3.3+、MSVC 2013+)
  • 无外部依赖:无需安装第三方库,仅需头文件/源码集成
  • 跨平台:支持 Linux、macOS、Windows 等主流操作系统

2. 源码集成

eventpp 是头文件/源码级库,无需编译安装,集成步骤如下:

  1. 下载源码:从 GitHub eventpp 仓库 下载最新源码
  2. 项目集成:将源码中的 eventpp/include/eventpp/ 目录复制到项目中,在代码中通过 #include <eventpp/xxx.h> 引入对应头文件
  3. 编译配置:编译时指定 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 状态:执行成功
=== 所有任务处理完毕 ===

六、使用注意事项

  1. 回调生命周期管理 :若回调是类成员函数,需确保类实例生命周期长于事件触发周期,可使用 std::shared_ptr/std::weak_ptr 避免悬空指针
  2. 参数传递细节 :回调参数优先使用 const &(避免大对象拷贝),需修改参数时使用普通引用
  3. 避免循环事件:防止"A事件触发B事件,B事件又触发A事件"导致的无限循环
  4. 线程安全选择 :单线程场景用 EventBus(效率更高),多线程场景用 ConcurrentEventBus(线程安全)
  5. 过度异步警告:轻量回调(如状态更新、简单日志)无需异步,同步执行效率更高,仅对耗时操作异步化

七、总结

  1. eventpp 是轻量级事件总线库,核心价值是模块解耦与回调管理,无IO/事件循环能力,需搭配其他框架处理异步IO
  2. 核心流程:定义事件类型 → 创建事件总线 → 注册回调 → 触发事件 → (可选)注销回调
  3. 高级特性:线程安全总线、带返回值回调、事件过滤器、全局单例总线,满足复杂业务场景
  4. 耗时回调解决方案:借助线程池异步执行,避免阻塞事件触发线程
  5. 适用场景:C++ 项目模块通信、GUI 事件分发、回调管理优化、无IO依赖的事件驱动场景
相关推荐
夏幻灵2 小时前
指针在 C++ 中最核心、最实用的两个作用:“避免大数据的复制” 和 “共享”。
开发语言·c++
FPGAI2 小时前
C++学习之函数
c++·学习
一粒麦仔2 小时前
Django架构详解:从MTV设计模式到企业级应用实践
后端
OldBirds2 小时前
烧脑时刻:Dart 中异步生成器与流
前端·后端
老马95272 小时前
事务工具类
数据库·后端
CC.GG2 小时前
【C++】STL----封装红黑树实现map和set
android·java·c++
violet-lz2 小时前
C++ 内存分区详解
开发语言·jvm·c++
汤姆yu2 小时前
基于springboot的林业资源管理系统
java·spring boot·后端
软件管理系统2 小时前
基于Spring Boot的医疗服务系统的设计与实现
java·spring boot·后端