C++消息框架-基於sigslot

1、簡介

上一篇文章Qt信號槽-原理分析主要講述了Qt的信號槽實現原理,固然除了Qt的信號槽之外,還有boost的signals,sigslot和sigc++等等,都是很是不錯的信號槽學習資料c++

  • boost的信號槽機制很強大,可是依賴了其餘模塊,並且對於大多數人來講,標準C++已經夠用
  • sigc++功能也不錯,可是文件數量比較多
  • sigslot只有一個頭文件,很是輕量,並且現有功可以咱們使用

瞭解sigslot用法能夠參考sigslots的簡單例子這篇文章,使用起來仍是相對簡單框架

本篇文章咱們主要是使用sigslots來作一個簡單的消息框架,主要是進行多個模塊之間消息通訊,固然也能夠是插件之間通訊。函數

咱們的框架總的來講是一個簡化版的消息通訊機制,學習起來也比較輕鬆,若是用於實際的工程項目的話,還須要進一步的優化。學習

以下圖所示,是我本身畫的類圖,咱們經過signal1來發送消息,並傳遞給全部的Receiver,這裏的接收者簡單來講能夠是一個類,若是是想複雜一些,也能夠是一個插件,後邊我會單獨講述怎麼加載插件dll

測試






2、消息

一般不一樣模塊之間傳遞消息時咱們須要定義一個消息結構,他能夠做爲函數回調時的參數,而後咱們會根據參數中的惟一標識,來區分不一樣的消息,或者判斷是否是咱們想要處理的消息。優化

/** 消息結構*/
struct Message
{
    std::string m_strMessage; ///消息類型  惟一ID
    void *      m_pUserData;  ///發送的數據
};

如Message結構中的m_strMessage變量,他惟一標識了消息的類型,咱們只須要判斷是咱們想處理的消息類型時,執行處理代碼便可。this

sigslot庫中的信號最多支持8個參數,但是在我門平常的開發工做中,可能會存在一些特殊的場景,超過8個參數;除此以外,根據參數類型的不一樣,往復雜裏寫咱們可能須要寫大量的適配工做。在這裏咱們使用一個簡單的小技巧,經過void *來轉發咱們的數據,也就是m_pUserData,這樣無論多少數據,咱們均可以封裝到一個變量中。.net

m_pUserData裏邊咱們能夠存儲任意類型的數據,只要咱們在處理事件的時候知道怎麼取出數據便可。插件

3、發送者

知道觀察者模式的同窗應該都知道,被觀察的對象(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);
}

4、接收者

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 << "收到一條新消息:";
        }
    }
};

5、功能測試

下面咱們寫兩個實際的消息接收類,來測試下消息框架

一、消息接收類

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";
        }
    }
};

二、測試代碼

測試代碼以下,咱們構造了一個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;
}

三、測試結果

最終測試結果以下

  • 接收者1處理了消息類型爲「1」的事件,並打印了testReceiver1:send2Receiver1
  • 接收者2處理了消息類型爲「2」的事件,並打印了testReceiver2:send2Receiver2

6、源碼

須要源碼的留郵箱,如今的csdn簡直太坑爹了。。。




轉載聲明:本站文章無特別說明,皆爲原創,版權全部,轉載請註明:朝十晚八 or Twowords

相關文章
相關標籤/搜索