C++11开发的消息总线库
创始人
2024-02-04 17:06:35
0

消息总线是一种降低耦合的一种技术,对象间只通过消息练习,不通过依赖或者关联。将复杂对象关系简化,降低复杂度,提高程序的维护性。
消息总线是一种解耦合的程序的设计方式,可以让对象之间的依赖和继承用消息注册来联系。

消息总线的关键技术
1、通用消息的定义
本质其实是让所有的对象之间的联系都通过消息来联系,所以,要定义一种通用的消息格式,让所有的对象都能使用接受。
2、消息的注册
让所有的对象注册感兴趣的消息。
3、消息分发
通过消息总线分发消息,让所有的接收者能收到并处理消息。

接下来就围绕上述这三点进行详细的解析:
1、那通用消息如何定义呢?

主题 + 泛型函数的签名

主题可以是任意一个对象,也可以是任意函数,字符串或者是整型等其他类型。

泛型函数:

std::function

std::function作为消息类型
R 函数返回类型
Args… 是一个可变参数模板,代表了任意个数类型的参数

设置好消息格式后,如何注册呢?

消息的注册:

某个对象注册以后,当需要使用的时候,会告诉总线,总线遍历消息列表,查找对应的消息和消息的接收者,找到以后再广播消息。
泛型函数可能是所有的可调用对象,例如:普通函数,成员函数,函数对象,std::function和lamda表达式。要统一这些接口,就是将这些全部转换为std::function。

使用lamda表达式转换为std::function的方法如下:
function_traits.hpp

#pragma once
#include 
#include 
//普通函数.
//函数指针.
//function/lambda.
//成员函数.
//函数对象.//转换为std::function和函数指针. 
template
struct function_traits;//普通函数.
template
struct function_traits
{
public:enum { arity = sizeof...(Args) };typedef Ret function_type(Args...);typedef Ret return_type;using stl_function_type = std::function;typedef Ret(*pointer)(Args...);templatestruct args{static_assert(I < arity, "index is out of range, index must less than sizeof Args");using type = typename std::tuple_element>::type;};typedef std::tuple>...> tuple_type;typedef std::tuple>...> bare_tuple_type;
};//函数指针.
template
struct function_traits : function_traits {};//std::function.
template 
struct function_traits> : function_traits {};//member function.
#define FUNCTION_TRAITS(...)\
template \
struct function_traits : function_traits{};\

FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)//函数对象.
template
struct function_traits : function_traits {};template 
typename function_traits::stl_function_type to_function(const Function& lambda)
{return static_cast::stl_function_type>(lambda);
}template 
typename function_traits::stl_function_type to_function(Function&& lambda)
{return static_cast::stl_function_type>(std::forward(lambda));
}template 
typename function_traits::pointer to_function_pointer(const Function& lambda)
{return static_cast::pointer>(lambda);
}#pragma once
#pragma once
#include 
#include 
#include 
#include struct Any
{Any(void) : m_tpIndex(std::type_index(typeid(void))) {}Any(const Any& that) : m_ptr(that.Clone()), m_tpIndex(that.m_tpIndex) {}Any(Any && that) : m_ptr(std::move(that.m_ptr)), m_tpIndex(that.m_tpIndex) {}//创建智能指针时,对于一般的类型,通过std::decay来移除引用和cv符,从而获取原始类型template::type, Any>::value, U>::type> Any(U && value) : m_ptr(new Derived < typename std::decay::type>(std::forward(value))),m_tpIndex(std::type_index(typeid(typename std::decay::type))) {}bool IsNull() const { return !bool(m_ptr); }template bool Is() const{return m_tpIndex == std::type_index(typeid(U));}//将Any转换为实际的类型templateU& AnyCast(){if (!Is()){std::cout << "can not cast " << typeid(U).name() << " to " << m_tpIndex.name() << std::endl;throw std::logic_error{ "bad cast" };}auto derived = dynamic_cast*> (m_ptr.get());return derived->m_value;}Any& operator=(const Any& a){if (m_ptr == a.m_ptr)return *this;m_ptr = a.Clone();m_tpIndex = a.m_tpIndex;return *this;}Any& operator=(Any&& a){if (m_ptr == a.m_ptr)return *this;m_ptr = std::move(a.m_ptr);m_tpIndex = a.m_tpIndex;return *this;}private:struct Base;typedef std::unique_ptr BasePtr;struct Base{virtual ~Base() {}virtual BasePtr Clone() const = 0;};templatestruct Derived : Base{templateDerived(U && value) : m_value(std::forward(value)) { }BasePtr Clone() const{return BasePtr(new Derived(m_value));}T m_value;};BasePtr Clone() const{if (m_ptr != nullptr)return m_ptr->Clone();return nullptr;}BasePtr m_ptr;std::type_index m_tpIndex;
};

2、保存注册消息

使用主题,将需要某个主题的对象收到特定的消息,这才是我们真正想要用的。对比这个,就和QT中的信号槽,发送某个信号,注册对应的发送者和接收者,注册对应的槽函数。

之前说过消息是各种调用对象转换的std::function类型,所以这些消息可能是各种各样的,就需要就不同类型 的消息保存起来,但是C++又不能直接保存各种类型到一个容器里,所以将对象的类型擦除(使用Any类型来擦除对象的类型)。

std::unordered_multimapm_map
string:键为主题
Any:消息类型的字符串

//注册可调用对象
template 
void Attach(const string& strTopic, const F& f)
{auto func = to_function(f);Add(strTopic,std::move(func));
}//注册成员函数
template 
void Attach(const string& strTopic, void(C::*f)(Args...) const, const P& p)
{std::function func = [&p, f](Args... args){return (*p.*f)(std::forward(args)...);}Add(strTopic, std::move(func));
}template 
void Add(const string& strTopic, F&&f)
{string strKey = strTopic + typeid(F).name();m_map.emplace(std::move(strKey),f);
}

对于非成员函数的可调用对象,先通过to_function将其转换为std::function类型之后再将std::function转换为Any,擦除类型,最后将消息key和消息对象保存起来。

MessageBus.hpp

#pragma once
#include 
#include 
#include 
#include "Any.hpp"
#include "function_traits.hpp"
#include "NonCopyable.hpp"using namespace std;class MessageBus : NonCopyable
{
public://注册消息templatevoid Attach(F&& f, const string& strTopic = ""){auto func = to_function(std::forward(f));Add(strTopic, std::move(func));}//发送消息templatevoid SendReq(const string& strTopic = ""){using function_type = std::function;string strMsgType = strTopic + typeid(function_type).name();auto range = m_map.equal_range(strMsgType);for (Iterater it = range.first; it != range.second; ++it){auto f = it->second.AnyCast < function_type >();f();}}templatevoid SendReq(Args&&... args, const string& strTopic = ""){using function_type = std::function;string strMsgType = strTopic + typeid(function_type).name();auto range = m_map.equal_range(strMsgType);for (Iterater it = range.first; it != range.second; ++it){auto f = it->second.AnyCast < function_type >();f(std::forward(args)...);}}//移除某个主题, 需要主题和消息类型templatevoid Remove(const string& strTopic = ""){using function_type = std::function; //typename function_traits::stl_function_type;string strMsgType = strTopic + typeid(function_type).name();int count = m_map.count(strMsgType);auto range = m_map.equal_range(strMsgType);m_map.erase(range.first, range.second);}private:templatevoid Add(const string& strTopic, F&& f){string strMsgType = strTopic + typeid(F).name();m_map.emplace(std::move(strMsgType), std::forward(f));}private:std::multimap m_map;typedef std::multimap::iterator Iterater;
};

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...