在现代 C++ 开发中,事件驱动编程模式十分常见。传统实现往往依赖于继承与虚函数,导致代码耦合度高、可维护性差。C++17 标准库新增的 std::variant 为我们提供了一种更轻量、类型安全的方式来实现事件系统。下面通过一个完整示例,演示如何使用 std::variant、std::visit 和 std::function 搭建一个通用且可扩展的事件框架。
1. 事件类型定义
#include <variant>
#include <string>
#include <vector>
#include <functional>
#include <iostream>
// ① 定义几种可能的事件负载
struct UserLoginEvent {
std::string username;
};
struct FileOpenEvent {
std::string file_path;
};
struct ErrorEvent {
int error_code;
std::string message;
};
// ② 用 variant 包装所有事件
using Event = std::variant<UserLoginEvent, FileOpenEvent, ErrorEvent>;
2. 事件总线(EventBus)
class EventBus {
public:
using Handler = std::function<void(const Event&)>;
// 注册处理器
void subscribe(const Handler& h) {
handlers_.push_back(h);
}
// 发布事件
void publish(const Event& e) {
for (const auto& h : handlers_) {
h(e);
}
}
private:
std::vector <Handler> handlers_;
};
3. 事件处理实现
// ③ 对每种事件写专门的处理函数
void handleLogin(const UserLoginEvent& e) {
std::cout << "[Login] 用户 " << e.username << " 登陆成功。\n";
}
void handleFileOpen(const FileOpenEvent& e) {
std::cout << "[FileOpen] 打开文件: " << e.file_path << "\n";
}
void handleError(const ErrorEvent& e) {
std::cerr << "[Error] (" << e.error_code << "): " << e.message << "\n";
}
4. 统一处理器(使用 std::visit)
// ④ 把各类处理器统一包装成一个 std::function
EventBus::Handler makeEventHandler() {
return [](const Event& e) {
std::visit(overloaded {
[](const UserLoginEvent& ev) { handleLogin(ev); },
[](const FileOpenEvent& ev) { handleFileOpen(ev); },
[](const ErrorEvent& ev) { handleError(ev); }
}, e);
};
}
// Helper: overloaded 用法
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
5. 完整示例
int main() {
EventBus bus;
// 注册统一处理器
bus.subscribe(makeEventHandler());
// 发布不同类型的事件
bus.publish(UserLoginEvent{"alice"});
bus.publish(FileOpenEvent{"/tmp/example.txt"});
bus.publish(ErrorEvent{404, "文件未找到"});
return 0;
}
6. 扩展与优化
-
分离主题(Topic)
若事件数量庞大,可在Event前面加一个枚举字段或使用std::map按主题分组注册处理器。 -
异步处理
在EventBus::publish中可把事件放入线程安全队列,另起工作线程进行消费,避免阻塞主线程。 -
类型安全的订阅
(handler)`,只接受指定类型的事件。
可以提供模板化的 `subscribe -
内存优化
若事件数据较大,考虑使用std::variant<std::reference_wrapper<const T>>或指针包装,减少拷贝。
7. 小结
std::variant让事件携带的负载在编译期保持类型安全,避免运行时的dynamic_cast。std::visit与overloaded组合,提供了清晰直观的多分支处理逻辑。- 事件总线模式与
std::function的组合,既保持了灵活性,又保证了运行时效率。
通过以上示例,读者可以快速掌握如何利用 C++17 的新特性,构建一个简洁、可维护且高效的事件驱动系统。