亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

C++實現委托和消息反饋模板

2019-11-17 05:07:53
字體:
來源:轉載
供稿:網友

  摘要:本文簡單介紹并比較了用于實現消息反饋的幾種常見技術,其中具體介紹了利用C++模板技術來實現類型安全的委托的要點和限制,可以作為理解qt/gtk+等UI庫的信號反饋機制的入門文章。

  正文:我寫過不少C++程序,寫過庫也寫過客戶程序。一般庫都會提供一些好用的類供客戶程序使用,不少庫還可以讓客戶程序響應庫內的某些事件。比如MFC/ATL/VCL提供消息響應,很多ActiveX提供自定義消息響應,甚至是系統底層的中斷調用都可以列入這個范疇。然而,正是以上這些“反向”的調用讓我覺得很煩惱。

  繼續+多態

  乍一看是理所當然的選擇,庫中的類把響應處理函數設置為虛函數,客戶程序可以繼續這個類并且重載響應函數。以某個Socket類為例,可以提供一個OnRecv函數用來響應網絡數據包到達的處理??蛻舫绦蛑恍枰剌dOnRecv并進行自己的處理就可以了。

strUCt Socket { // base class
virtual void OnRecv();
};
stuct MySocket { // your event-handle class
virtual void OnRecv() { /* do sth here ... */ }
}
  疑問:很多時候這樣做實在很煩,非凡是做小程序的時候,或者需要快速做原型的時候,一眼望去小小的程序一上來就繼續了一大堆東西,頗為不爽。只是想著能省事一點,希望能像那些腳本語言一樣快速綁定消息響應,而不是以繼續開始工作——我已經害怕看到長長的類繼續樹了,很多時候根本不必要繼續整個類;又或者某些類只提供一個接口而不是具體的類又或者需要多重繼續,處理都有一定麻煩;最麻煩的莫過于有時候需要改變響應處理,難道繼續好幾個下來么——這么多虛表也是浪費啊。

  點評:為了使用Socket就必須繼續Socket,這可以說是Socket的設計的問題。假如需要實現類似的功能的話,可以寫成如下,雖然和繼續 Socket 沒有多少本質的差別,不過確實把消息處理類和Socket的實現扯開了。:

struct SocketEventHandler {
virtual void OnRecv() { /* ... */ }
virtual void OnSend() { /* ... */ }
};
struct Socket {
void set_handler( SocketEventHandler* h ) { handler_ = h; }
PRivate:
SocketEventHandler* handler_;
};
struct MyHandler : SocketEventHandler {
void OnRecv() { ... }
};
Socket s;
MyHandler h;
s.set_handler( &h );
  忽然之間,我感到一陣迷茫,非??释环N簡單明確的表達方法。丟開繼續,我們還有什么把戲?我不禁想起了c時代的回調函數……

  回調函數(CallBack)

  非常簡單,就是一個函數指針。剛才的OnRecv可以寫成這樣

struct Socket {
void OnRecv() { if(OnRecvHandle!=NULL) OnRecvHandle(); }
void (*OnRecvHandle) ();
};
  客戶程序只需要編寫一個MyOnRecv函數,并且賦值給OnRecvHandle就可以了

void MyOnRecv(); // your event-handle function
Socket foo;
foo.OnRecvHandle = MyOnRecv;
  疑問:非常簡單,不需要繼續類就可以處理,而且隨時可以替換不同的處理函數。其實多態的本質也是函數指針,只不過多態是用vtable統一治理函數指針。回調函數要非凡注重函數指針是否為空的問題,因此最好外面在包裝一層判定過程。回調函數最大問題在于類型不安全,顯式指針這東西……不說也罷……翻了一下智能指針和模版,我發現了一根稻草…… 委托(Delegation)

  委托是什么呢?這個名詞似乎是時尚的代名詞,我仿佛看到學java/c#的兄弟們在嘲笑我們的落后……其實,property不也可以算是一種委托嗎?說白了不就是智能指針么?


  我覺得委托最本質的是提供一種類型安全的動態消息響應轉移機制。

  以前,我對委托一無所知,我覺得無非就是一個類型安全的智能指針,而所謂的Multi-Cast Delegation無非就是一個智能指針數祖……是不是還有Any-Cast Delegation呢?我不知道,也許有吧,無非就是智能指針數祖+隨機數發生器……

  但是,實際上并不是那么簡單。你可以把我剛才說的函數指針封裝一下弄一個類封裝起來,不過,這直接導致某個消息的響應只能是固定死的函數指針類型,甚至不能是可愛的Functor或者是某個類的成員函數。你可能會跟我抬杠說這怎么可能,不是可以用template實現么?我們來看一個例子

  假設某個委托類 Dummy_Delegation 擁有一個成員函數用來連接處理函數 template<class T> void Dummy_Delegation::Connect(T _F); 沒錯,_F可以不一定函數指針,也可以是Functor,我們利用_F()來呼叫響應函數,一切看起來是多么美好——但是,很不幸,這個_F無法保存下來供消息產生的時候呼叫……

  一切都因為這個該死的template<class T>,你無法在Dummy_Delegation內定義一個T類型的變量或者指針來保存_F。退一萬步說,你把T作為整個Dummy的模版,還是避免不了在模版實例化的時候定死類型。于是,整個Delegation的通用性大打折扣……

  實際上,我們希望有這么一種Delegation,他可以把消息響應動態綁定到任何一個類的成員函數上只要函數類型一致。注重,這里說的是任何一個類。這就要求我們屏蔽信號發生器和響應類之間的耦合關系,即,讓他們相互都不知道對方是誰甚至不知道對方的類型信息。

  這個方法可行么?Yes!

  橋式委托(Bridge Delegation) ---- 利用泛型+多態來實現

  請答應我杜撰一個名詞:橋式委托(Bridge Delegation)

  實現這么一個東西真的很有意思,其實,像gtk+/qt很多需要"信號/反饋"(signal/slot)的系統都是這么實現的。

  說到GP和Template,那真的可以算是百家爭鳴了,就像boost和loki還在爭奪新的C++標準智能指針的地位打得不可開交。而Functor這個東西有是很多GP algo的基礎,比如sort/for_each等等。

  整個橋式委托的結構如下圖:

Signal <>-------->* Interface
^

Implementation<Receiver> -------------> Receiver

  我們搭建了一個Interface/Implementation的橋用來連接Singal和Receiver,這樣就可以有效隔開雙方的直接耦合。用之前我們的Socket類來演示如下:

struct Socket {
Signal OnRecv;
};
  一個Receiver可以是一個function比如 void OnRecv1() 也可以是一個Functor:

struct OnRecv2_t {
void Operator() ();
} OnRecv2;
  我們可以這樣使用這個橋式委托

Socket x;
x.OnRecv.ConnectSlot(OnRecv1); //或者 x.OnRecv.ConnectSlot(OnRecv2());
  當消息產生調用 x.OnRecv()的時候,用戶指定的OnRecv1或者OnRecv2就會響應。

  我們來看看如何實現這個橋:首先是一個抽象類

struct DelegationInterface {
virtual ~DelegationInterface() {};
virtual void Action() = 0;
};
  然后才是模版類Impl:

template<class T>
struct DelegationImpl : public DelegationInterface {
T _FO;
DelegationImpl(T _S) :_FO(_S) { }
virtual void Action() { _FO(); }
};
  注重我們上面的圖示,這個DelegationImpl類是跟Receiver相關聯的,也就是說這個Impl類知道所有的Receiver細節,于是他可以從容地調用Receiver()。再次留意這個繼續關系,對了,一個virutal的Action函數!利用多態性質,我們可以根據Receiver來實例化DelegationImpl類,卻可以利用提供一致的訪問Action的Interface,這就是整座橋的秘密所在——利用多態下層隔離細節!

  再看看我們的Signal類:

struct Signal {
DelegationInterface* _PI;

Signal() :_PI(NULL) {}
~Signal() { delete _PI; }

void operator()() { if(_PI) _PI->Action(); }
template<class T> void ConnectSlot(T Slot) {
delete _PI; _PI = new DelegationImpl<T>(Slot);
}
};
  顯然,Signal類利用了 DelegationInterface* 指針_PI來呼叫響應函數。而完成這一切連接操作的正是這個奇妙的ConnectSlot的函數。對了!上次討論模版函數的時候就說了這個T類型無法保存,但是這里用橋避開了這個問題。利用模版函數的T做為DelegationImpl的實例化參數,一切就這么簡單地解決了~

  你也許可能會抗議,認為我繞了一大圈又繞回了一開始我煩惱的繼續/多態上面來了。呵呵。其實,你有沒有發現,我們這個Singal/Bridge Delegation/Receive的體系是固定的一套東西,你在實際使用中并不需要自己去繼續去處理重載,你只需要好好地Connect到正確的Slot就可以了。這也可以算是一種局部隱含的繼續吧。

  接下來我們要討論一下這個橋式委托的性能消耗以及擴展和局限性問題 橋式委托的進一步研究

  看過上面的橋式委托之后,可能會有點懷疑他的性能,需要一個interface指針一個functor類/函數指針,調用的時候需要一次查vtable,然后再一次做operator()調用。
其實,這些消耗都不算很大的,整個橋式委托的類結構是簡單的,相對于前面說的繼續整個類之類的做法開銷還是比較小的,而且又比函數指針通用而且類型安全。最重要的是,剛才的Signal可以方便地改寫為Multi-Cast Delegation即一個信號引發多個響應——把Singal內部的DelegationInterface*指針改為一個指針隊列就可以了;-)

  不過,我們剛才實現的橋式委托只能接收函數指針和functor,不能接收另外一個類的成員函數,有時候這是非常有用的動作。比如設置一個按鈕Button的OnClick事件的響應為一個MsgBox的Show方法。當然,MsgBox還有其他非常多的方法,這樣就可以不用局限于把MsgBox當成一個functor了。

  我們要改寫剛才的整個橋來實現這個功能,在這里需要你對指向成員函數得指針有所了解。

// 新版的橋式委托,可以接收類的成員函數作為響應
struct DelegationInterface {
virtual ~DelegationInterface() {};
virtual void Run() = 0;
};

template<class T>
struct DelegationImpl : public DelegationInterface {
typedef void (T::* _pF_t)(); // 指向類T成員函數的指針類型

DelegationImpl(T* _PP, _pF_t pF) :_P(_PP), _PF(pF) {}
virtual void Run() {
if(_P) { (_P->*_PF)(); } // 成員函數調用,很別扭的寫法(_P->*_PF)();
}

T* _P; // Receiver類
_pF_t _PF; // 指向Receiver類的某個成員函數
};
struct Signal
{
DelegationInterface* _PI;
Signal() :_PI(NULL) {}
void operator() () { if(_PI) _PI->Run(); }

// 新的ConnectSlot需要指定一個類以及這個類的某個成員函數
template<class T>
void ConnectSlot(T& recv, void (T::* pF)()) { // pF這個參數真夠別扭的
_PI = new DelegationImpl<T>(&recv, pF);
}
};
  注重:ConnectSlot方法的pF參數類型非常復雜,也可以簡化如下,即把這個類型檢測推到DelegationImpl類去完成,而不在Connect這里進行么?編譯器可以正確識別。對于模板來說,很多復雜的參數類型都可以用一個簡單的類型代替,不用關心細節,就象上面用一個F代替void (T::*)()。有時候能改善可讀性,有時候象反。

template<class T, class F>
void ConnectSlot( T& recv, F pF ) {
PI_ = new DelegationImpl<T>(&recv,pF);
}
  這個新版怎么用呢,很簡單的。比如你的MsgBox類有一個成員函數Show,你可以把這個作為響應函數:

MsgBox box;
Socket x; // Socket還跟舊的版本一樣
x.OnRecv.ConnectSlot(box, &MsgBox::Show);
  注重上面這里引用成員函數指針的寫法,一定不能寫成box.Show,呵呵,希望你還記得成員函數是屬于類公共的東西,不是某個實例的私有產品。大家不妨進一步動一下腦筋,把新版的Signal和舊版的Signal結合一下,你就可以獲得一個功能超強的Delegation系統了。

  點評:用signal的辦法確實可以方便地動態替換處理函數,不過這是以每個可能被處理的消息都要在每個對象中占用一個 signal 的空間為代價的。而且,需要動態改變處理函數的應用我已經不記得什么時候見過了。即使有,也可以通過在override的virtual函數里自己處理實現,雖說麻煩,但也是可能的。此外,以上代碼并不夠規范,下劃線加大寫字母開頭的標識符是保留給語言的實現用的。

  結論

  我們關于橋式委托的討論接近尾聲了,大家也許已經發現了一個巨大的問題:上面的橋式委托無法給相應操作傳遞參數?。?!是的,這是一個巨大的矛盾——你必須自己實現帶一個參數的橋、自己實現帶2個參數的橋……就像stl的functor一樣,你無法做到參數通用處理,必須區分unary_functor、binary_functor……你不得不這么做:

template<class P1>
struct DelegationInterface { virtual void Run(P1 param) = 0; };
template<class T, class P1>
struct DelegationImpl : public DelegationInterface<P1> {
......
}
template<class P1>
struct Signal {
DelegationInterface<P1> *_PI;
......
}
  好慘! 要自己寫這么多橋啊?C++語法這么不給面子……當然了,你可以繞路來實現,比如用一個通用的打包參數來包裝多個參數,用宏定義來處理各種情況,當然也可以用預處理來實現——我這里要說的,同情一下QT吧,不要整天抱怨他的signal/slot體系需要預處理是在擴展語言——設身處地地想一想,C++提供給我們的就這有這些了,一個小小的參數是我們這些signal/slot抹不去的傷痛。


  幸運的是,在C++標準委員會不斷的努力之下,這些情況開始有所改善。boost庫之中的signal庫可以直接支持可變參數的委托;同時,越來越多的元語言技術也引入了C++之中。雖然目前支持這些新特性的編譯器還比較少,不過這已經是非常巨大的進步了,讓我們期待吧……

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品va在线| 久热精品视频在线观看| 色伦专区97中文字幕| 丝袜美腿亚洲一区二区| 欧美高清无遮挡| 色综合色综合网色综合| 欧美成人精品激情在线观看| 亚洲欧美激情精品一区二区| 亚洲精选在线观看| 日韩在线免费视频| 欧美日本亚洲视频| 啪一啪鲁一鲁2019在线视频| 亚洲视频在线视频| 欧美第一淫aaasss性| 亚洲国产高清自拍| 国产一区在线播放| 欧美日韩成人免费| 久久人91精品久久久久久不卡| 中文字幕精品在线| 亚洲电影免费观看| 久久91精品国产91久久久| 久色乳综合思思在线视频| 国产精品三级美女白浆呻吟| 777777777亚洲妇女| 日韩女优在线播放| 成人欧美一区二区三区在线湿哒哒| 久久久国产一区二区三区| 日韩欧美国产激情| 亚洲成人久久网| 欧美黑人一级爽快片淫片高清| 久久亚洲国产精品| 国产自产女人91一区在线观看| 久久久极品av| 日韩电视剧免费观看网站| 97视频在线观看网址| 俺去亚洲欧洲欧美日韩| 国产精品日韩在线观看| 庆余年2免费日韩剧观看大牛| 正在播放欧美视频| 欧美日韩国产一区在线| 91精品久久久久久久久青青| 国产美女主播一区| 91亚洲国产成人久久精品网站| 91chinesevideo永久地址| 91av视频在线| 51精品在线观看| 亚洲成人1234| 日韩久久免费视频| 国产日韩精品电影| 日韩电影中文字幕| 精品无人区乱码1区2区3区在线| 亚洲欧美制服中文字幕| 中文字幕在线成人| 亚洲欧美日韩爽爽影院| 97成人精品视频在线观看| 性日韩欧美在线视频| 国产精品日韩一区| 九色成人免费视频| 亚洲国产精品成人一区二区| 国产亚洲在线播放| 欧美视频在线免费看| 91超碰中文字幕久久精品| 日本a级片电影一区二区| 日韩av电影中文字幕| 欧美激情精品久久久久久免费印度| 国产在线高清精品| 亚洲a中文字幕| 久久久久久久久久国产| 亚洲精品久久久一区二区三区| 日韩精品在线观看一区二区| 欧美最顶级的aⅴ艳星| 久久久久久网址| 欧美成人在线影院| 亚洲国产美女久久久久| 久久人人爽人人爽爽久久| 奇米一区二区三区四区久久| 亚洲桃花岛网站| 亚洲精品色婷婷福利天堂| 黄色一区二区三区| 国产啪精品视频网站| 久久av在线看| 插插插亚洲综合网| 亚洲人成网在线播放| 激情亚洲一区二区三区四区| 精品亚洲精品福利线在观看| 久久影院资源站| 日韩欧美在线播放| 日本19禁啪啪免费观看www| 免费不卡欧美自拍视频| 在线观看日韩www视频免费| 久久国产精品网站| 91在线观看欧美日韩| 亚洲大胆美女视频| 久久精品欧美视频| 久久精品99久久久久久久久| 欧美专区在线观看| 91久久久久久久久| 热久久视久久精品18亚洲精品| 精品国产乱码久久久久久天美| 欧美巨大黑人极品精男| 欧美成人性生活| 亚洲mm色国产网站| 久久久久久久一区二区三区| 亚洲激情中文字幕| 中文字幕不卡在线视频极品| 日韩成人网免费视频| 91视频国产精品| 91香蕉嫩草神马影院在线观看| 热99精品里视频精品| 欧美日韩在线第一页| 成人av.网址在线网站| 欧美另类精品xxxx孕妇| 欧美日韩日本国产| 欧美日韩综合视频网址| 91精品视频免费观看| 国产精品露脸自拍| 日韩美女av在线| 久久久女人电视剧免费播放下载| 日韩欧美在线播放| 一道本无吗dⅴd在线播放一区| 欧美另类在线播放| 国产欧美精品va在线观看| 亚洲免费伊人电影在线观看av| 成人中文字幕+乱码+中文字幕| 国产精品中文字幕久久久| 欧美精品videossex88| 国产有码在线一区二区视频| 欧美精品生活片| 国产日韩换脸av一区在线观看| 日韩在线小视频| 欧美老女人xx| 亚洲精品视频网上网址在线观看| 一区二区欧美激情| 欧美性xxxx在线播放| 欧美日韩美女在线观看| 在线观看精品自拍私拍| 成人黄在线观看| 日韩av一区二区在线| 国产啪精品视频| 欧美日韩美女在线| 在线观看成人黄色| 色妞在线综合亚洲欧美| 久久视频免费观看| 日韩av在线影院| 日韩欧美在线一区| 久久亚洲精品国产亚洲老地址| 亚洲第一精品久久忘忧草社区| 另类少妇人与禽zozz0性伦| 国产69精品99久久久久久宅男| 69久久夜色精品国产69| 日韩欧亚中文在线| 亚洲人成在线免费观看| 亚洲国产欧美一区二区丝袜黑人| 在线观看欧美日韩| 亚洲黄色在线观看| 日本免费一区二区三区视频观看| 国产亚洲a∨片在线观看| 国产美女直播视频一区| 亚洲欧美在线一区二区| 538国产精品一区二区在线| 国产精品夜间视频香蕉| 精品国产欧美一区二区五十路| 中文字幕av一区| 俺去亚洲欧洲欧美日韩|