聊聊C++跨类通信机制之消息总线及其实现

如果没有怎么写过项目,更确切地说是没有用面向对象的语言写过项目。就不会明白跨类通信这个需求是多么刚需。

为什么要跨类通信?把所有的逻辑都写在一个类中,都是一家人,那就不需要通信了啊。是,确实是这样,当功能不是很多的时候,这样做确实好。但是,随着项目的规模增大,各种功能堆积,各种模块的增加,会使得这个类非常臃肿与庞大。臃肿庞大就意味着不好维护和扩展。

因此,我们需要把功能划分出来,把模块划分得细一些,尽量做到类的职责单一且明显。这样可以做到高内聚,低耦合

既然需要很多类,那么类与类之间必然会存在一些关系,如何来维护?对,就是跨类通信,沟通起来。

想想之前,两个.c文件需要通信时,我们会怎么做,搞一个全局变量就可以了啊。

对,跨类通信就是通过全局变量来做到的(好像也只有这个办法哦),这里也要说下原理:我们实现一个程序,这个程序有很多类,那么最终这些类肯定是组成了一个进程,而全局变量在一个进程中是共享的。

既然全局变量可以做到跨类通信,那么任意两个类之间需要通信就要搞一个全局变量。这样子全局变量也变得臃肿,这不是我们想要的。

概念

我们可以参考计算机硬件的一个概念:消息总线。这个概念其实是把网状结构,变成了星性结构,类与类的通信有一个中心机构(消息总线)管理。

在这里插入图片描述

设计

消息总线,有消息二字。这是什么意思呢?我需要你干什么,我给你发个消息,你就把这个事给干了。对,这就是消息总线的设计。大体设计就是这样,现在来看看消息总线需要哪些原料。

首先,我们要提前写好处理消息的函数,然后等待别人发消息,这里,我们要把消息和函数对应起来,因此这里选择std::multimap,为什么不选择std::map呢,因为,同一个消息可能有几个函数对之感兴趣;

设计的过程中一定要考虑到同步和异步,尤其是异步,因为潜意识中都会写同步,不会怎么考虑异步,考虑这样一种情况:我现在很渴,给你发个消息,让你去楼下买水,然而你不怎么锻炼,买水送给我这个过程需要一个小时,你说我是等一个小时再喝水还是等个十分钟,看看你回来没,如果没回来就叫别人去买?这里就牵扯到消息总线的同步和异步,这个在程序中就有体现。

基于上面需要异步以及等待,因此消息总线还需要一个线程池支持异步以及一个相对任务定时器来等待。

然后就跑下面的流程图就行了:

在这里插入图片描述

这里提供MessageBus的声明,可以看看消息总线有哪些功能:

#include <experimental/any>

class MessageBus
{
public:
    using any = std::experimental::any;

    typedef std::function<void(any, any)> FUN;
    typedef std::function<void(any, any)> AsynTimeoutCallback;

public:
    MessageBus() = default;
    ~MessageBus() = default;
private:
    MessageBus(const MessageBus &) = delete;
    MessageBus(MessageBus &&) = delete;
    MessageBus & operator = (const MessageBus &) = delete;
    MessageBus & operator = (MessageBus &&) = delete;

public:
    uint32_t RigisterMessageByMainType(uint32_t mainType, const FUN & callback);
    uint32_t RigisterMessage(uint32_t mainType, uint32_t minorType, const FUN & callback);

    void CacelMessageByID(uint32_t id);
    void CacelMessageByMainType(uint32_t mainType);
    void CacelMessage(uint32_t mainType, uint32_t mimorType);

    void SyncSendMessageByMainType(uint32_t mainType, any mainAny = any(), any minorAny = any());
    void SyncSendMessage(uint32_t mainType, uint32_t minorType, any mainAny = any(), any minorAny = any());

    void AsynSendMessageByMainType(uint32_t mainType, std::chrono::seconds d = std::chrono::seconds(1),
            AsynTimeoutCallback = [] (any, any) {}, any mainAny = any(), any minorAny = any());
    void AsynSendMessage(uint32_t mainType, uint32_t minorType, std::chrono::seconds d = std::chrono::seconds(1),
            AsynTimeoutCallback = [] (any, any) {}, any mainAny = any(), any minorAny = any());

private:
    void DoFunctionByMainType(uint32_t mainType, any mainAny, any minorAny);
    void DoFunction(uint32_t mainType, uint32_t minorType, any mainAny, any minorAny);
private:
    struct Blob
    {
        Blob() {}
        Blob(uint32_t id, const FUN & fun) : id(id), fun(fun) {}
        uint32_t id;
        FUN fun = nullptr;
    };
    struct CompByMainType
    {
        bool operator () (const uint32_t val, const std::pair< std::pair<uint32_t , uint32_t>, std::shared_ptr<Blob> > & ele);
        bool operator () (const std::pair< std::pair<uint32_t , uint32_t>, std::shared_ptr<Blob> > & ele, const uint32_t val);
    };
private:
    std::mutex mutex_;
    uint32_t id_;
    std::multimap< std::pair<uint32_t, uint32_t>, std::shared_ptr<Blob> > blobs_;

    ThreadPool pool_;
    RelativeTimer relativeTimer_;
};

实现

bool MessageBus::CompByMainType::operator()(
        const std::pair<std::pair<uint32_t, uint32_t>, std::shared_ptr<MessageBus::Blob>> &ele, const uint32_t val)
{
    return ele.first.first < val;
}

bool MessageBus::CompByMainType::operator()(const uint32_t val,
                                            const std::pair<std::pair<uint32_t, uint32_t>, std::shared_ptr<MessageBus::Blob>> &ele)
{
    return val < ele.first.first;
}

uint32_t MessageBus::RigisterMessage(uint32_t mainType, uint32_t minorType, const MessageBus::FUN &callback)
{
    std::lock_guard<std::mutex> lock(mutex_);
    blobs_.emplace(std::make_pair(mainType, minorType), std::make_shared<Blob>(id_, callback));
    return id_++;
}

uint32_t MessageBus::RigisterMessageByMainType(uint32_t mainType, const MessageBus::FUN &callback)
{
    std::lock_guard<std::mutex> lock(mutex_);
    blobs_.emplace(std::make_pair(mainType, -1), std::make_shared<Blob>(id_, callback));
    return id_++;
}

void MessageBus::CacelMessage(uint32_t mainType, uint32_t mimorType)
{
    std::lock_guard<std::mutex> lock(mutex_);
    auto range = blobs_.equal_range(std::make_pair(mainType, mimorType));
    blobs_.erase(range.first, range.second);
}

void MessageBus::CacelMessageByMainType(uint32_t mainType)
{
    std::lock_guard<std::mutex> lock(mutex_);
    auto range = std::equal_range(std::begin(blobs_), std::end(blobs_), mainType, CompByMainType {});
    blobs_.erase(range.first, range.second);
}

void MessageBus::CacelMessageByID(uint32_t id)
{
    std::lock_guard<std::mutex> lock(mutex_);
    blobs_.erase(std::find_if(std::begin(blobs_), std::end(blobs_),
            [id] (const std::pair< std::pair<uint32_t , uint32_t >, std::shared_ptr<Blob> > & ele)
    {
        return id == ele.second->id;
    }));
}

void MessageBus::DoFunction(uint32_t mainType, uint32_t minorType, any mainAny, any minorAny)
{
    std::vector< std::shared_ptr<Blob> > vec;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        auto range = blobs_.equal_range(std::make_pair(mainType, minorType));
        for (auto it = range.first; it != range.second; vec.push_back(it++->second)) {}
    }

    std::for_each(std::begin(vec), std::end(vec), [=] (const std::shared_ptr<Blob> & blob)
    {
        blob->fun(mainAny, minorAny);
    });
}

void MessageBus::DoFunctionByMainType(uint32_t mainType, MessageBus::any mainAny, MessageBus::any minorAny)
{
    std::vector< std::shared_ptr<Blob> > vec;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        auto range = std::equal_range(std::begin(blobs_), std::end(blobs_), mainType, CompByMainType {});
        for (auto it = range.first; it != range.second; vec.push_back(it++->second)) {}
    }

    std::for_each(std::begin(vec), std::end(vec), [=] (const std::shared_ptr<Blob> & blob)
    {
        blob->fun(mainAny, minorAny);
    });
}

void MessageBus::SyncSendMessage(uint32_t mainType, uint32_t minorType, MessageBus::any mainAny, MessageBus::any minorAny)
{
    DoFunction(mainType, minorType, mainAny, minorAny);
}

void MessageBus::SyncSendMessageByMainType(uint32_t mainType, MessageBus::any mainAny, MessageBus::any minorAny)
{
    DoFunctionByMainType(mainType, mainAny, minorAny);
}

void MessageBus::AsynSendMessage(uint32_t mainType, uint32_t minorType, std::chrono::seconds d,
        MessageBus::AsynTimeoutCallback callback, MessageBus::any mainAny, MessageBus::any minorAny)
{
    std::shared_future<void> future = pool_.Submit([=] ()
    {
        DoFunction(mainType, minorType, mainAny, minorAny);
    });

    relativeTimer_.AddTimerTask(std::to_string((static_cast<uint64_t>(mainType) << 32) + minorType), [=] ()
    {
        if (future.wait_for(std::chrono::seconds::zero()) != std::future_status::ready) callback(mainAny, minorAny);
    }, d, false, true);
}

void MessageBus::AsynSendMessageByMainType(uint32_t mainType, std::chrono::seconds d,
        MessageBus::AsynTimeoutCallback callback, MessageBus::any mainAny, MessageBus::any minorAny)
{
    std::shared_future<void> future = pool_.Submit([=] ()
    {
        DoFunctionByMainType(mainType, mainAny, minorAny);
    });

    relativeTimer_.AddTimerTask(std::to_string((static_cast<uint64_t>(mainType) << 32) + static_cast<uint32_t>(-1)),
            [=] ()
    {
        if (future.wait_for(std::chrono::seconds::zero()) != std::future_status::ready) callback(mainAny, minorAny);
    }, d, false, true);
}

细节

std::experimental::any

为了实现函数签名的统一性,参数类型使用any,标准库中的any是C++17才有的内容,这里使用<experimental>头文件中的anyany可以擦除类型信息,有点类似void *指针。

struct CompByMainType

我设置了主消息号和副消息号,这样可以同一类的消息分在一个主消息号中。有利于管理和维护,但是有时候我们需要给所有注册主消息号的函数发消息,因此需要这个比较函数。

异步

仔细看看异步发送消息那个函数。把消息处理函数提交到线程池中,然后开一个定时任务,如果在规定时间内,消息处理函数没有完成,那么就调用超时处理函数;

future是用了std::shared_future,这是因为std::future是不可复制的,而 l a m b d a lambda lambda表达式需要复制一次future,因此选用可复制的std::shared_future

测试

消息总线通常用来跨类通信,由于是测试的小程序,就在main函数中测试消息总线,只需要观察控制台的输出是否符合期望就好了。

using namespace std::experimental;

MessageBus messageBus;

int main()
{
    messageBus.RigisterMessage(0, 0, [] (any, any)
    {
        std::cout << "0 + 0" << std::endl;
    });
    messageBus.RigisterMessage(0, 0, [] (any, any)
    {
        std::cout << "00 + 00" << std::endl;
    });
    messageBus.RigisterMessageByMainType(0, [] (any, any)
    {
        std::cout << "0" << std::endl;
    });
    messageBus.RigisterMessageByMainType(2, [] (any, any)
    {
        /* // */std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "2222" << std::endl;
    });

    int data = 1, data2 = 1;
    messageBus.RigisterMessageByMainType(3, [] (any mainAny, any)
    {
        int *p = any_cast<int *>(mainAny);
        *p = 2;
        std::cout << "3333" << std::endl;
    });
    messageBus.RigisterMessageByMainType(4, [] (any mainAny, any)
    {
        int *p = any_cast<int *>(mainAny);
        *p = 2;
        std::cout << "4444" << std::endl;
        std::this_thread::sleep_for(std::chrono::hours(1));
    });

    messageBus.SyncSendMessage(0, 0);
    std::cout << "-------" << std::endl;
    messageBus.SyncSendMessageByMainType(0);
    std::cout << "-------" << std::endl;

    messageBus.AsynSendMessageByMainType(2, std::chrono::seconds(2), [] (any, any)
    {
        std::cout << "timeout" << std::endl;
    });

    std::cout << "-------" << std::endl;
    std::cout << "before data : " << data << std::endl;
    messageBus.SyncSendMessageByMainType(3, &data);
    std::cout << "after data : " << data << std::endl;
    std::cout << "-------" << std::endl;

    std::cout << "-------" << std::endl;
    std::cout << "before data2 : " << data2 << std::endl;
    messageBus.AsynSendMessageByMainType(4, std::chrono::seconds(2), [] (any mainAny, any)
    {
        int *p = any_cast<int *>(mainAny);
        *p = 2;
        std::cout << "timeout" << std::endl;
    }, &data2);
    std::cout << "after data2 : " << data2 << std::endl;
    std::cout << "-------" << std::endl;

    while (true)
    {
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
    return 0;
}

参考:

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
C++模板类的实现机制是通过在编译时进行类型具体化来生成具体的类和对象。模板类本身是抽象的,也就是类型化的,使用时必须进行类型具体化。具体的类是通过将模板类中的类型参数替换为具体的类型来生成的。例如,引用\[1\]中的代码中,模板类C是从模板类A派生而来的,通过具体化模板类C<int>,编译器知道了父类A的数据类型是int,从而能够正确地分配内存和启用父类的构造函数。 在具体化模板类时,编译器需要知道模板类的数据类型具体是什么样的,这样才能确定父类所占的内存大小和如何分配内存。因此,只有在数据类型固定的情况下,才能进行模板类的具体化和继承操作。 总结起来,C++模板类的实现机制是通过在编译时进行类型具体化,将抽象的模板类转化为具体的类和对象,以便正确地分配内存和启用父类的构造函数。 #### 引用[.reference_title] - *1* *2* [C++重要知识清单:泛型编程(函数模板和类模板机制)包含模板机制的底层实现原理](https://blog.csdn.net/weixin_39568744/article/details/105950208)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [C++模板的实现机制](https://blog.csdn.net/qq_41306849/article/details/120020140)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值