上一篇文章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簡直太坑爹了。。。