C++消息框架-基于sigslot
一、简介
上一篇文章Qt信号槽-原理分析主要讲述了Qt的信号槽实现原理,当然除了Qt的信号槽以外,还有boost的signals,sigslot和sigc++等等,都是非常不错的信号槽学习资料
- boost的信号槽机制很强大,但是依赖了其他模块,而且对于大多数人来说,标准C++已经够用
- sigc++功能也不错,但是文件数量比较多
- sigslot只有一个头文件,非常轻量,而且现有功能够我们使用
了解sigslot用法可以参考sigslots的简单例子这篇文章,使用起来还是相对简单
本篇文章我们主要是使用sigslots来做一个简单的消息框架,主要是进行多个模块之间消息通信,当然也可以是插件之间通信。
我们的框架总的来说是一个简化版的消息通信机制,学习起来也比较轻松,如果用于实际的工程项目的话,还需要进一步的优化。
如下图所示,是我自己画的类图,我们通过signal1来发送消息,并传递给所有的Receiver,这里的接收者简单来说可以是一个类,如果是想复杂一些,也可以是一个插件,后边我会单独讲述怎么加载插件dll
二、消息
通常不同模块之间传递消息时我们需要定义一个消息结构,他可以作为函数回调时的参数,然后我们会根据参数中的唯一标识,来区分不同的消息,或者判断是不是我们想要处理的消息。
/** 消息结构*/
struct Message
{
std::string m_strMessage; ///消息类型 唯一ID
void * m_pUserData; ///发送的数据
};
如Message结构中的m_strMessage变量,他唯一标识了消息的类型,我们只需要判断是我们想处理的消息类型时,执行处理代码即可。
sigslot库中的信号最多支持8个参数,可是在我门日常的开发工作中,可能会存在一些特殊的场景,超过8个参数;除此之外,根据参数类型的不同,往复杂里写我们可能需要写大量的适配工作。在这里我们使用一个简单的小技巧,通过void *来转发我们的数据,也就是m_pUserData,这样不管多少数据,我们都可以封装到一个变量中。
m_pUserData里边我们可以存储任意类型的数据,只要我们在处理事件的时候知道怎么取出数据即可。
三、发送者
知道观察者模式的同学应该都知道,被观察的对象(Subject)维护了一个观察者(Observer)列表,当我们的被观察者发生变化的时候,被观察者可以遍历自己维护的观察者列表,然后将变化通知给观察者。同样的我们这个框架也类似于这样的设计,只不过我们的发送者没有维护接收者列表,而是通过信号槽的绑定机制,把发送者的发送函数绑定到了接收者的接收函数,而且是一对多绑定,也就是说我们的信号可以对多个槽。
这样的设计下,发送者和接收者还是有一定的耦合,后边有时间优化的话,我会引入一个第三方的管理者,帮助我们让发送者和接收者进行关联,这样也能提供最大的灵活性。
如下是发送者的代码
class Sender
{
public:
void sendMessage(const std::string & = "", void * = 0);
virtual void addReceiver(Receiver *);
virtual void removeReceiver(Receiver *);
private:
sigslot::signal1<Message *> m_pSender;
};
发送者包含3个接口,发送消息、添加接收者和移除接收者。而最重要的地方当属我们的m_pSender变量,他是sigslot库封装的信号,这个库总共提供了8种信号,但是我们只使用参数为1个的信号,因为我们把参数封装成了一个结构,也就是说我们的参数被包装成了一个对象。
下面我们来分析下这三个函数
1、发送消息函数
发送消息时,我们需要指定消息的id和消息的内容,并构造为一个Message对象,作为信号参数发送出去,这样槽函数就可以收到我们发送的内容。
特别注意,Message对象的销毁是在所有槽函数执行完毕以后
void Sender::sendMessage(const std::string & msgID, void * data)
{
Message msg;
msg.m_strMessage = msgID;
msg.m_pUserData = data;
m_pSender(&msg);//消息的接收者执行完后 msg被销毁
}
2、新增一个接收者函数
新增接收者时,我们只需要使用connect把接收者的函数绑定到我们的信号上即可。是不是特别简单呢!
void Sender::addReceiver(Receiver * receiver)
{
m_pSender.connect(receiver, &Receiver::onMessage);
}
3、移除一个接收者函数
移除接受者时,我们只需要使用disconnect把接收者从绑定的接收者列表中移除即可。
void Sender::removeReceiver(Receiver * receiver)
{
m_pSender.disconnect(receiver);
}
四、接收者
sigslot库要求我们,如果想要被signals信号连接,则我们的类必须从sigslot::has_slots<>继承,这里我们封装了一个Receiver类,方便后续我们写更多的功能类。这个类里我添加了一个onMessage函数,这个函数就是我们处理信号的回调函数,当signals发送信号时,onMessage函数就会被调用,我们在这里处理自己关注的事件即可。
class Receiver : public sigslot::has_slots<>
{
public:
virtual void onMessage(Message *) = 0;
};
我们在写新功能时,只需要继承Receiver类,并实现onMessage函数即可。
Message就是我们发送信号时构造的对象,里边包含了消息的类型ID和用户数据,我们只需要根据消息ID就可以知道,这个消失是否是我们需要处理的,如果需要处理,那我们将需要小心翼翼的从void *中取出相关用户数据,进行处理。
例如下面代码,是一个简单的消息页面,当我们收到消息回调时,我们通过判断消息ID,他就是我们需要处理的消息NEW_ITEM_REPORT,然后我们打印了一句话,
这里只是简单举了一个例子,实际开发中,代码复杂度往往都比较高
class newsPage : public Receiver{
public:
newsPage(Sender * sender) {
sender->addReceiver(this);//把自己加入到消息接收者队列中
}
virtual void onMessage(Message * msg) {
if (msg->m_strMessage == "NEW_ITEM_REPORT") {
std::cout << "收到一条新消息:";
}
}
};
五、功能测试
下面我们写两个实际的消息接收类,来测试下消息框架
1、消息接收类
a、测试类1
消息接收类我们必须从Receiver来继承,并且需要把自己添加到信号对象的消息接收列表中。
处理消息时,当我们发现消息ID是字符串“1”时,是我们要处理的消息,则打印消息内容
class testReceiver1 : public Receiver{
public:
testReceiver1(Sender * sender) {
sender->addReceiver(this);//把自己加入到消息接收者队列中
}
virtual void onMessage(Message * msg) {
if (msg->m_strMessage == "1") {
std::cout << "testReceiver1:" << (char *)msg->m_pUserData << "\n";
}
}
};
b、测试类2
消息接收类2同类1一样,只是处理消息时,判断的消息ID不一样,这里不做解释,
class testReceiver2 : public Receiver{
public:
testReceiver2(Sender * sender) {
sender->addReceiver(this);//把自己加入到消息接收者队列中
}
virtual void onMessage(Message * msg) {
if (msg->m_strMessage == "2") {
std::cout << "testReceiver2:" << (char *)msg->m_pUserData << "\n";
}
}
};
2、测试代码
测试代码如下,我们构造了一个Sender发送者,并声明了两个消息接收对象,然后直接使用send对象开始发送消息
实际使用过程中,Sender可能不会这样直接暴露出来,通常是通过一个单例来进行管理
int main()
{
Sender send;
testReceiver1 rece1(&send);
testReceiver2 rece2(&send);
send.sendMessage("1", "Receiver1 deal");
send.sendMessage("2", "Receiver2 deal");
getchar();
return 0;
}
3、测试结果
最终测试结果如下
- 接收者1处理了消息类型为“1”的事件,并打印了testReceiver1:send2Receiver1
- 接收者2处理了消息类型为“2”的事件,并打印了testReceiver2:send2Receiver2
六、源码
需要源码的留邮箱,现在的csdn简直太坑爹了。。。
![]() |
![]() |
很重要--转载声明
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。
C++消息框架-基于sigslot的更多相关文章
- django 消息框架 message
在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户. 对于这个需求,Django提供了基于Cookie或者会话的消息框架messages,无论是匿名用户还是认证的用户 ...
- 优化技术专题-线程间的高性能消息框架-深入浅出Disruptor的使用和原理
前提概要 简单回顾 jdk 里的队列: 阻塞队列: ArrayBlockingQueue主要通过:数组(Object[])+ 计数器(count)+ ReetrantLock的Condition (n ...
- 第六章:Django 综合篇 - 10:消息框架 message
在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户. 对于这个需求,Django提供了基于Cookie或者会话的消息框架messages,无论是匿名用户还是认证的用户 ...
- 完全开源Android网络框架 — 基于JAVA原生的HTTP框架
HttpNet网络请求框架基于HttpUrlConnection,采用Client + Request + Call的请求模型,支持https默认证书,数字安全证书.支持http代理!后续将会实现队列 ...
- [编织消息框架][netty源码分析]2 eventLoop
eventLoop从命名上看是专门处理事件 事件系统主要由线程池同队列技术组成,有以下几个优点 1.任务出队有序执行,不会出现错乱,当然前提执行线程池只有一个 2.解偶系统复杂度,这是个经典的生产者/ ...
- Siki_Unity_3-6_UI框架 (基于UGUI)
Unity 3-6 UI框架 (基于UGUI) 任务1&2&3&4:介绍 && 创建工程 UI框架: 管理场景中所有UI面板 控制面板之间的跳转 如果没有UI框 ...
- 第二百六十四节,Tornado框架-基于正则的动态路由映射分页数据获取计算
Tornado框架-基于正则的动态路由映射分页数据获取计算 分页基本显示数据 第一步.设置正则路由映射配置,(r"/index/(?P<page>\d*)", inde ...
- 第二百六十三节,Tornado框架-基于正则的动态路由映射
Tornado框架-基于正则的动态路由映射 1.在路由映射条件里用正则匹配访问路径后缀2.给每一个正则匹配规则(?P<设置名称>)设置一个名称,3.在逻辑处理的get()方法或post() ...
- ACE框架 基于共享内存的分配器 (算法设计)
继承上一篇<ACE框架 基于共享内存的分配器设计>,本篇分析算法部分的设计. ACE_Malloc_T模板定义了这样一个分配器组件 分配器组件聚合了三个功能组件:同步组件ACE_LOCK, ...
随机推荐
- 『深度应用』NLP机器翻译深度学习实战课程·壹(RNN base)
深度学习用的有一年多了,最近开始NLP自然处理方面的研发.刚好趁着这个机会写一系列NLP机器翻译深度学习实战课程. 本系列课程将从原理讲解与数据处理深入到如何动手实践与应用部署,将包括以下内容:(更新 ...
- 记录一则DG遭遇ORA-00088的案例
测试环境:RHEL 5.4 + Oracle 11.2.0.3 DG 现象:起初是在使用DG Broker进行switchover切换测试时,报错ORA-16775,提示有可能有数据丢失,不允许swi ...
- Java描述表达式求值的两种解法:双栈结构和二叉树
Java描述表达式求值的两种解法:双栈结构和二叉树 原题大意:表达式求值 求一个非负整数四则混合运算且含嵌套括号表达式的值.如: # 输入: 1+2*(6/2)-4 # 输出: 3.0 数据保证: 保 ...
- PythonI/O进阶学习笔记_1.抽象、面向对象、class/object/type
前言: 是自己在学习python进阶IO学习视频的时候的理解和笔记,因为很多都是本菜鸟学习时候的自己的理解,有可能理解有误. Content: - 抽象的概念和面向对象的概念?想要大概了解python ...
- 随笔编号-02 阿里云CentOS7系列三 -- 配置防火墙
前面讲到了安装JDK以及Tomcat.但是大家会发现,当我们访问 http:// XXX.XXX.XXX.XXX:8080/80 时候,tomcat 猫并没有出现.原因就是没有设置防火墙. 再次介绍下 ...
- Leetcode之深度优先搜索(DFS)专题-547. 朋友圈(Friend Circles)
Leetcode之深度优先搜索(DFS)专题-547. 朋友圈(Friend Circles) 深度优先搜索的解题详细介绍,点击 班上有 N 名学生.其中有些人是朋友,有些则不是.他们的友谊具有是传递 ...
- 18_init 函数的使用
1.init()函数是一个内置函数,在程序执行前会先执行init()函数,及在main()函数执行前执行 2.如果调用包里有init()函数,会先执行调用包的init()函数,在这执行本函数的init ...
- Oracle大量数据更新策略
生产上要修改某个产品的产品代号, 而我们系统是以产品为中心的, 牵一发而动全身, 涉及表几乎覆盖全部, 有些表数据量是相当大的, 达到千万, 亿级别. 单纯的维护产品代号的 SQL 是不难的, 但是性 ...
- Kubernetes 入门必备云原生发展简史
作者|张磊 阿里云容器平台高级技术专家,CNCF 官方大使 "未来的软件一定是生长于云上的"这是云原生理念的最核心假设.而所谓"云原生",实际上就是在定义一条能 ...
- CodeForces 1082 G Petya and Graph 最大权闭合子图。
题目传送门 题意:现在有一个图,选择一条边,会把边的2个顶点也选起来,最后会的到一个边的集合 和一个点的集合 , 求边的集合 - 点的集合最大是多少. 题解:裸的最大权闭合子图. 代码: #inclu ...

