订阅发布模块事例

背景

由于业务之间的解耦,通常我们在一个进程中会设计多个模块,模块之间的通信有时不需要消息队列,但是需要一个通信的机制。

示例1------消息内容使用基类

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <map>
#include <mutex>
#include <memory>

// 通用消息基类(可选,用于统一传递数据)
struct Message {
    virtual ~Message() = default;
};

class MessageBus {
public:
    using Callback = std::function<void(const std::shared_ptr<Message>&)>;

    // 获取单例
    static MessageBus& instance() {
        static MessageBus bus;
        return bus;
    }

    // 订阅:指定字符串主题
    void subscribe(const std::string& topic, Callback callback) {
        std::lock_guard<std::mutex> lock(mutex_);
        subscribers_[topic].push_back(std::move(callback));
    }

    // 发布:指定字符串主题和消息内容
    void publish(const std::string& topic, const std::shared_ptr<Message>& msg) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        auto it = subscribers_.find(topic);
        if (it == subscribers_.end()) return;

        // 复制回调列表,避免执行过程中修改容器
        auto callbacks = it->second;
        
        // 释放锁后执行,防止死锁
        lock.unlock();

        for (const auto& cb : callbacks) {
            try {
                cb(msg);
            } catch (...) {
                std::cerr << "Error in subscriber for topic: " << topic << std::endl;
            }
        }
    }

private:
    MessageBus() {}
    std::mutex mutex_;
    // Key: 主题名称 (String), Value: 回调列表
    std::map<std::string, std::vector<Callback>> subscribers_;
};

// ================= 测试示例 =================

struct OrderMsg : public Message {
    int orderId;
    OrderMsg(int id) : orderId(id) {}
};

struct LogMsg : public Message {
    std::string content;
    LogMsg(const std::string& c) : content(c) {}
};

int main() {
    auto& bus = MessageBus::instance();

    // 1. 订阅 "order" 主题
    bus.subscribe("order", [](const std::shared_ptr<Message>& msg) {
        auto order = std::dynamic_pointer_cast<OrderMsg>(msg);
        if (order) {
            std::cout << "[OrderModule] 收到订单: " << order->orderId << std::endl;
        }
    });

    // 2. 订阅 "log" 主题
    bus.subscribe("log", [](const std::shared_ptr<Message>& msg) {
        auto log = std::dynamic_pointer_cast<LogMsg>(msg);
        if (log) {
            std::cout << "[LogModule] 收到日志: " << log->content << std::endl;
        }
    });

    // 3. 发布消息
    bus.publish("order", std::make_shared<OrderMsg>(1001));
    bus.publish("log", std::make_shared<LogMsg>("System Start"));

    return 0;
}

示例2------消息类型模板化

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <map>
#include <mutex>
#include <memory>
#include <typeindex>

// ================= 1. 内部存储辅助类 =================
// 用于在 Map 中存储不同类型的回调,并管理数据的生命周期
class MessageBus {
public:
    static MessageBus& instance() {
        static MessageBus bus;
        return bus;
    }

    // 订阅:指定主题和回调
    template <typename T>
    void subscribe(const std::string& topic, std::function<void(const T&)> callback) {
        std::lock_guard<std::mutex> lock(mutex_);
        // 使用 type_index 确保同一主题下不同类型的回调隔离(可选,若约定主题唯一类型可省略)
        auto key = std::make_pair(topic, std::type_index(typeid(T)));
        
        // 包装回调:从 void* 恢复为 T&
        auto wrapper = [callback](void* data) {
            callback(*static_cast<const T*>(data));
        };
        
        registry_[key].push_back(wrapper);
    }

    // 发布:值传递,内部会拷贝一份数据
    template <typename T>
    void publish(const std::string& topic, const T& message) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        auto key = std::make_pair(topic, std::type_index(typeid(T)));
        auto it = registry_.find(key);
        if (it == registry_.end()) return;

        // 【关键】:拷贝消息数据,确保生命周期独立于发布者
        // 使用 shared_ptr 管理拷贝后的数据,确保所有回调执行期间数据有效
        auto dataCopy = std::make_shared<T>(message);
        
        auto callbacks = it->second;
        lock.unlock();

        for (const auto& cb : callbacks) {
            try {
                // 传递拷贝数据的指针,回调中转换为引用
                cb(dataCopy.get());
            } catch (...) {
                std::cerr << "Error in subscriber." << std::endl;
            }
        }
    }

private:
    MessageBus() {}
    std::mutex mutex_;
    
    // Key: <主题名, 类型信息>, Value: 类型擦除的回调列表
    using Key = std::pair<std::string, std::type_index>;
    std::map<Key, std::vector<std::function<void(void*)>>> registry_;
};

// ================= 2. 测试示例 =================

struct OrderEvent {
    int orderId;
    std::string customer;
    OrderEvent(int id, const std::string& name) : orderId(id), customer(name) {
        std::cout << "OrderEvent Created: " << orderId << std::endl;
    }
    ~OrderEvent() {
        std::cout << "OrderEvent Destroyed: " << orderId << std::endl;
    }
};

int main() {
    auto& bus = MessageBus::instance();

    // 订阅
    bus.subscribe<OrderEvent>("order", [](const OrderEvent& e) {
        std::cout << "[Subscriber] Received Order: " << e.orderId << ", " << e.customer << std::endl;
    });

    {
        // 发布:传入局部变量(值传递)
        OrderEvent localOrder(101, "Bob");
        std::cout << "--- Publishing ---" << std::endl;
        bus.publish("order", localOrder);
        std::cout << "--- Publish Call Finished ---" << std::endl;
    } 
    // localOrder 在此处销毁,但订阅者收到的拷贝依然有效

    return 0;
}
相关推荐
汉克老师3 小时前
2026年CSP-J初赛分数线-----预测
c++·csp-j·小学生·学c++编程
小保CPP4 小时前
OpenCV C++将多张图像合并为webp动图
c++·人工智能·opencv·计算机视觉
乐观勇敢坚强的老彭5 小时前
信奥C++一维数组笔记
开发语言·c++·笔记
码上有光5 小时前
异常和智能指针
java·大数据·c++·servlet·异常·智能指针
王维同学5 小时前
[原创][Windows C++]Explorer Shell 扩展、图标覆盖与 COM 服务器定位
c++·windows·注册表
code_pgf5 小时前
C++ 工程架构设计规范
c++·机器人
C++ 老炮儿的技术栈5 小时前
基于MFC+原生GDI手动自绘仪表盘控件
c语言·c++·visual studio·控件·工业控制·gdi·自绘
孬甭_5 小时前
C++ vector
开发语言·c++
小肝一下5 小时前
3. 单链表
c语言·数据结构·c++·算法·leetcode·链表·dijkstra