C++訊號槽

吳尼瑪發表於2017-12-19

什麼是訊號槽?

  • 簡單來說,訊號槽是觀察者模式的一種實現,或者說是一種昇華。
  • 一個訊號就是一個能夠被觀察的事件,或者至少是事件已經發生的一種通知;一個槽就是一個觀察者,通常就是在被觀察的物件發生改變的時候——也可以說是訊號發出的時候——被呼叫的函式;你可以將訊號和槽連線起來,形成一種觀察者-被觀察者的關係;當事件或者狀態發生改變的時候,訊號就會被髮出;同時,訊號發出者有義務呼叫所有註冊的對這個事件(訊號)感興趣的函式(槽)。
  • 訊號和槽是多對多的關係。一個訊號可以連線多個槽,而一個槽也可以監聽多個訊號。
  • 另外訊號可以有附加資訊。

使用訊號槽

  • 訊號槽是偉大的工具,但是如何能更好的使用它?相比於直接函式呼叫,有三點值得我們的注意。
    • 一個訊號槽的呼叫,可能會比直接函式呼叫耗費更多的時間/空間;
    • 可能不能使用 inline;
    • 對於程式碼閱讀者來說可能並不友好。
  • 使用訊號槽進行解耦,我們獲得的最大的好處是,連線兩端的物件不需要知道對方的任何資訊。
  • 你可以實現一個應用程式,其中每一個函式呼叫都是通過訊號來觸發的。這在技術上說是完全沒有問題的,然而卻是不大可行的,因為訊號槽的使用無疑會喪失一部分程式碼可讀性和系統效能。如何在這其中做出平衡,也是你需要考慮的很重要的一點。

##sigslot庫

  • 官方地址

  • C++中的訊號槽系統常用的有三種:boost的signals,sigslot,sigc++。其中sigslot庫是比較簡單好用的。

  • sigslot是一個執行緒安全、型別安全,用C++實現的sig/slot機制(sig/slot機制就是物件之間傳送和接收訊息的機制)的開原始碼庫。只有一個標頭檔案sigslot.h。

  • 基本功能有:

    1. connect
    2. disconnect
    3. emit
  • sigslot優點

    1. 不用擔心空回撥,當回撥物件析構時會自動disconnect
    2. 支援多執行緒,執行緒安全,有鎖
  • sigslot缺點

    1. 只能回撥void型別函式,不支援返回值。boost中的signals庫架構類似,支援返回值,但引入了boost中的其他庫
    2. slot沒有優先順序,不能動態調整回撥佇列中的先後順序
  • slot函式(被回撥的函式)就是普通的成員函式,但有以下限制:

    1. 返回值必須為void
    2. slot引數個數範圍為0-8個
    3. 實現slot的類必須繼承自has_slots<>
  • 前兩條是sigslot庫作者的限制,作者權衡各方面因素後做出的決定,如果你覺得有必要你可以修改sigslot程式碼取消該限制,而最後一條是sigslot的機制基礎,必須遵守,除非你自己重新寫個sigslot。

  • 需要注意的是:sigslot庫的設計,當傳送一個沒有連線的訊號時,不做任何處理,也不會有錯誤發出。

基本使用方式

  • 包含標頭檔案
#include "sigslot.h"
複製程式碼
  • 改動(“typename 必須前置於巢狀依賴型別名”)
//在sigslot.h的420,將:
typedef sender_set::const_iterator const_iterator;
    //改為:
typedef typename sender_set::const_iterator const_iterator;
複製程式碼
  • signal0~signal8:訊號類:作為類成員
class mySg
{
    sigc::signal0<>                 sg1;    // 無引數
    sigc::signal2<char*, double="">    sg2;    // 2個引數
}
複製程式碼
  • connection(槽函式:作為類成員,類需要繼承has_slots<>,且槽函式的返回值必須是void型別)
class mySlot: public : has_slots<>
{
public:
    void on_func1(){}                       // 無引數,與訊號對應
    void on_func2(char*, double)(){}        // 2個引數
};
mySg    sig;
mySlot  slt;
sig.sg1.conncent(&slt,&mySlot::on_func1);
sig.sg2.conncent(&slt,&mySlot::on_func2);
複製程式碼
  • disconnection(解除連線:可以使用disconnect()和disconnect_all())
sig.sg1.disconnect(&slt);
sig.sg1.disconnect_all();
複製程式碼
  • emiting(傳送訊號:可以直接使用()運算子,也可以呼叫signal的emit函式)
sig.sg1.emit();
sig.sg2("str",0.1);
複製程式碼

相關文章