You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 23:15:39