C++自定义信号总线优化:带数据事件回调的易用实现方案问询
C++ 信号总线(SignalBus)带参数事件的优雅实现方案
核心思路
利用C++模板特性,为每个信号类型维护强类型回调列表,避免void*转换或基类多态的类型匹配问题,让订阅者直接接收对应类型的事件数据。
完整实现代码
#include <functional> #include <unordered_map> #include <typeindex> #include <vector> #include <memory> class SignalBus { private: // 抽象回调容器基类,用于统一存储不同信号类型的回调容器 class CallbackContainerBase { public: virtual ~CallbackContainerBase() = default; virtual void invoke(const void* signal) const = 0; }; // 针对具体信号类型的回调容器,存储强类型回调 template<typename SignalType> class CallbackContainer : public CallbackContainerBase { public: using Callback = std::function<void(const SignalType&)>; std::vector<Callback> callbacks; void invoke(const void* signal) const override { // 安全的静态类型转换,发布时的信号类型与容器类型严格匹配 const SignalType& typed_signal = *static_cast<const SignalType*>(signal); for (const auto& cb : callbacks) { cb(typed_signal); } } }; // 存储所有信号类型对应的回调容器 std::unordered_map<std::type_index, std::unique_ptr<CallbackContainerBase>> m_containers; public: /** * 订阅指定类型的信号 * @param callback 回调函数,接收const引用类型的信号参数 */ template<typename SignalType> void Subscribe(std::function<void(const SignalType&)> callback) { auto type_idx = std::type_index(typeid(SignalType)); // 如果该信号类型的容器不存在,创建新容器 if (!m_containers.contains(type_idx)) { m_containers[type_idx] = std::make_unique<CallbackContainer<SignalType>>(); } // 将回调加入对应容器的回调列表 auto container = static_cast<CallbackContainer<SignalType>*>(m_containers[type_idx].get()); container->callbacks.push_back(std::move(callback)); } /** * 发布指定类型的信号 * @param signal 要发布的信号对象 */ template<typename SignalType> void Publish(const SignalType& signal) { auto type_idx = std::type_index(typeid(SignalType)); // 找到对应信号类型的回调容器并执行所有回调 if (auto it = m_containers.find(type_idx); it != m_containers.end()) { it->second->invoke(&signal); } } };
使用示例
1. 定义带数据的信号类型
// 示例信号:携带整数和字符串数据 struct DataSignal { int code; std::string content; }; // 无参数信号(兼容原有逻辑) struct EmptySignal {};
2. 订阅与发布
#include <iostream> void HandleDataSignal(const DataSignal& signal) { std::cout << "Received DataSignal: code=" << signal.code << ", content=" << signal.content << std::endl; } int main() { SignalBus bus; // 订阅带参数信号:lambda直接接收强类型参数 bus.Subscribe<DataSignal>([](const DataSignal& s) { std::cout << "Lambda handler: " << s.content << std::endl; }); // 订阅带参数信号:传入自由函数 bus.Subscribe<DataSignal>(HandleDataSignal); // 订阅无参数信号(保持原有简洁性) bus.Subscribe<EmptySignal>([]() { std::cout << "Empty signal received" << std::endl; }); // 发布带参数信号 bus.Publish(DataSignal{200, "Hello SignalBus"}); // 发布无参数信号 bus.Publish(EmptySignal{}); return 0; }
方案优势
- 强类型安全:编译期即可检查信号类型与回调参数的匹配性,彻底避免void*转换的潜在风险
- 使用简洁:订阅时无需手动类型转换,lambda或函数直接接收对应类型的参数,代码可读性高
- 扩展性强:新增信号类型只需定义结构体/类,无需修改SignalBus核心逻辑
- 兼容原有逻辑:无缝支持无参数信号的订阅与发布,保持原有使用体验
扩展优化方向
- 支持回调的取消订阅:为每个订阅返回唯一标识,用于从回调列表中移除
- 支持移动语义:将回调参数改为
SignalType&&,适合传递大型对象 - 线程安全:添加互斥锁保护回调容器的访问,支持多线程环境下的订阅与发布
内容的提问来源于stack exchange,提问作者dimanchique
相关产品推荐
相关产品推荐

