订阅发布模块事例

背景

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

示例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;
}
相关推荐
凉茶钱11 小时前
多态:从虚函数语法到虚表寻址,一次讲透动态绑定
开发语言·c++·多态
01二进制代码漫游日记11 小时前
[C++]之内存管理(一)
jvm·c++·学习
codebolt12 小时前
C++程序crash分析和内存检测
开发语言·c++
ShineWinsu12 小时前
对于Qt:QLabel常见属性的解析
c++·qt·面试·笔试·gui·标签·qlabel
zwc321043205512 小时前
Day 5:threshold与inRange阈值分割
c++·图像处理·人工智能·opencv·算法·计算机视觉
小徐不徐说12 小时前
数据库存储基础:理解表结构、主键、事务与 BLOB
开发语言·数据库·c++·qt·程序设计
草莓熊Lotso13 小时前
【C++ 基于微服务的即时通讯系统】网关基建:cpp-httplib 与 WebSocketpp 从入门到实战
linux·开发语言·数据库·c++·redis·微服务·云原生
Sakura_j_h_1 天前
EasyEDA PCB 快速设计Skills
c++·python·github
程序喵大人1 天前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
jimy11 天前
C++ 中的 `T&&`:右值引用与转发引用
开发语言·c++