上一篇文章Qt信号槽-原理分析主要讲述了Qt的信号槽实现原理,固然除了Qt的信号槽之外,还有boost的signals,sigslot和sigc++等等,都是很是不错的信号槽学习资料c++
了解sigslot用法能够参考sigslots的简单例子这篇文章,使用起来仍是相对简单框架
本篇文章咱们主要是使用sigslots来作一个简单的消息框架,主要是进行多个模块之间消息通讯,固然也能够是插件之间通讯。函数
咱们的框架总的来讲是一个简化版的消息通讯机制,学习起来也比较轻松,若是用于实际的工程项目的话,还须要进一步的优化。学习
以下图所示,是我本身画的类图,咱们经过signal1来发送消息,并传递给全部的Receiver,这里的接收者简单来讲能够是一个类,若是是想复杂一些,也能够是一个插件,后边我会单独讲述怎么加载插件dll
测试
一般不一样模块之间传递消息时咱们须要定义一个消息结构,他能够做为函数回调时的参数,而后咱们会根据参数中的惟一标识,来区分不一样的消息,或者判断是否是咱们想要处理的消息。优化
/** 消息结构*/ struct Message { std::string m_strMessage; ///消息类型 惟一ID void * m_pUserData; ///发送的数据 };
如Message结构中的m_strMessage变量,他惟一标识了消息的类型,咱们只须要判断是咱们想处理的消息类型时,执行处理代码便可。this
sigslot库中的信号最多支持8个参数,但是在我门平常的开发工做中,可能会存在一些特殊的场景,超过8个参数;除此以外,根据参数类型的不一样,往复杂里写咱们可能须要写大量的适配工做。在这里咱们使用一个简单的小技巧,经过void *来转发咱们的数据,也就是m_pUserData,这样无论多少数据,咱们均可以封装到一个变量中。.net
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个的信号,由于咱们把参数封装成了一个结构,也就是说咱们的参数被包装成了一个对象。
下面咱们来分析下这三个函数
发送消息时,咱们须要指定消息的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被销毁 }
新增接收者时,咱们只须要使用connect把接收者的函数绑定到咱们的信号上便可。是否是特别简单呢!
void Sender::addReceiver(Receiver * receiver) { m_pSender.connect(receiver, &Receiver::onMessage); }
移除接受者时,咱们只须要使用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 << "收到一条新消息:"; } } };
下面咱们写两个实际的消息接收类,来测试下消息框架
消息接收类咱们必须从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"; } } };
消息接收类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"; } } };
测试代码以下,咱们构造了一个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; }
最终测试结果以下
须要源码的留邮箱,如今的csdn简直太坑爹了。。。