自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

細(xì)說C++委托和消息反饋模板

開發(fā) 后端
本文簡單介紹并比較了用于實現(xiàn)消息反饋的幾種常見技術(shù),其中詳細(xì)介紹了利用C++模板技術(shù)來實現(xiàn)類型安全的委托的要點(diǎn)和限制,可以作為理解qt/gtk+等UI庫的信號反饋機(jī)制的入門文章。

  C++實現(xiàn)委托消息反饋模板:

  繼承+多態(tài)

  乍一看是理所當(dāng)然的選擇,庫中的類把響應(yīng)處理函數(shù)設(shè)置為虛函數(shù),客戶程序可以繼承這個類并且重載響應(yīng)函數(shù)。以某個Socket類為例,可以提供一個OnRecv函數(shù)用來響應(yīng)網(wǎng)絡(luò)數(shù)據(jù)包到達(dá)的處理。客戶程序只需要重載OnRecv并進(jìn)行自己的處理就可以了。

  1. struct Socket { // base class  
  2. virtual void OnRecv();  
  3. };  
  4. stuct MySocket { // your event-handle class  
  5. virtual void OnRecv() { /* do sth here ... */ }  

  疑問:很多時候這樣做實在很煩,特別是做小程序的時候,或者需要快速做原型的時候,一眼望去小小的程序一上來就繼承了一大堆東西,頗為不爽。只是想著能省事一點(diǎn),希望能像那些腳本語言一樣快速綁定消息響應(yīng),而不是以繼承開始工作——我已經(jīng)害怕看到長長的類繼承樹了,很多時候根本不必要繼承整個類;又或者某些類只提供一個接口而不是具體的類又或者需要多重繼承,處理都有一定麻煩;最麻煩的莫過于有時候需要改變響應(yīng)處理,難道繼承好幾個下來么——這么多虛表也是浪費(fèi)啊。

  點(diǎn)評:為了使用Socket就必須繼承Socket,這可以說是Socket的設(shè)計的問題。如果需要實現(xiàn)類似的功能的話,可以寫成如下,雖然和繼承 Socket 沒有多少本質(zhì)的差別,不過確實把消息處理類和Socket的實現(xiàn)扯開了。

  1. struct SocketEventHandler {  
  2. virtual void OnRecv() { /* ... */ }  
  3. virtual void OnSend() { /* ... */ }  
  4. };  
  5. struct Socket {  
  6. void set_handler( SocketEventHandler* h ) { handler_ = h; }  
  7. private:  
  8. SocketEventHandler* handler_;  
  9. };  
  10. struct MyHandler : SocketEventHandler {  
  11. void OnRecv() { ... }  
  12. };  
  13. Socket s;  
  14. MyHandler h;  
  15. s.set_handler( &h ); 

  丟開繼承,我們有沒有一種簡單明確的表達(dá)方法呢?我不禁想起了c時代的回調(diào)函數(shù)……

  回調(diào)函數(shù)(CallBack)

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

  1. struct Socket {  
  2. void OnRecv() { if(OnRecvHandle!=NULL) OnRecvHandle(); }  
  3. void (*OnRecvHandle) ();  
  4. };  

  客戶程序只需要編寫一個MyOnRecv函數(shù),并且賦值給OnRecvHandle就可以了

  1. void MyOnRecv(); // your event-handle function  
  2. Socket foo;  
  3. foo.OnRecvHandle = MyOnRecv; 

  疑問:非常簡單,不需要繼承類就可以處理,而且隨時可以替換不同的處理函數(shù)。其實多態(tài)的本質(zhì)也是函數(shù)指針,只不過多態(tài)是用vtable統(tǒng)一管理函數(shù)指針?;卣{(diào)函數(shù)要特別注意函數(shù)指針是否為空的問題,因此***外面在包裝一層判斷過程,回調(diào)函數(shù)***問題在于類型不安全。

#p#

  委托(Delegation)

  委托是什么呢?委托最本質(zhì)的是提供一種類型安全的動態(tài)消息響應(yīng)轉(zhuǎn)移機(jī)制。

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

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

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

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

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

  這個方法可行么?Yes!

  橋式委托(Bridge Delegation) ---- 利用泛型+多態(tài)來實現(xiàn)

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

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

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

  整個橋式委托的結(jié)構(gòu)如下圖:

  1. Signal <>-------->* Interface  
  2. ^  
  3. |  
  4. Implementation<Receiver> -------------> Receiver 

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

  1. struct Socket {  
  2. Signal OnRecv;  
  3. }; 

  一個Receiver可以是一個function比如 void OnRecv1() 也可以是一個Functor:

  1. struct OnRecv2_t {  
  2. void operator() ();  
  3. } OnRecv2; 

  我們可以這樣使用這個橋式委托

  1. Socket x;  
  2. x.OnRecv.ConnectSlot(OnRecv1); //或者 x.OnRecv.ConnectSlot(OnRecv2());  

  當(dāng)消息產(chǎn)生調(diào)用 x.OnRecv()的時候,用戶指定的OnRecv1或者OnRecv2就會響應(yīng)。

  我們來看看如何實現(xiàn)這個橋:首先是一個抽象類

  1. struct DelegationInterface {  
  2. virtual ~DelegationInterface() {};  
  3. virtual void Action() = 0;  
  4. };  

  然后才是模版類Impl:

  1. templateclass T>  
  2. struct DelegationImpl : public DelegationInterface {  
  3. T _FO;  
  4. DelegationImpl(T _S) :_FO(_S) { }  
  5. virtual void Action() { _FO(); }  
  6. };  

  注意我們上面的圖示,這個DelegationImpl類是跟Receiver相關(guān)聯(lián)的,也就是說這個Impl類知道所有的Receiver細(xì)節(jié),于是他可以從容地調(diào)用Receiver()。再次留意這個繼承關(guān)系,對了,一個virutal的Action函數(shù)!利用多態(tài)性質(zhì),我們可以根據(jù)Receiver來實例化DelegationImpl類,卻可以利用提供一致的訪問Action的Interface,這就是整座橋的秘密所在——利用多態(tài)下層隔離細(xì)節(jié)!

  再看看我們的Signal類:

  1. struct Signal {  
  2. DelegationInterface* _PI;  
  3.  
  4. Signal() :_PI(NULL) {}  
  5. ~Signal() { delete _PI; }  
  6.  
  7. void operator()() { if(_PI) _PI->Action(); }  
  8. templateclass T> void ConnectSlot(T Slot) {  
  9. delete _PI; _PI = new DelegationImpl<T>(Slot);  
  10. }  
  11. }; 

  顯然,Signal類利用了 DelegationInterface* 指針_PI來呼叫響應(yīng)函數(shù)。而完成這一切連接操作的正是這個奇妙的ConnectSlot的函數(shù)。對了!上次討論模版函數(shù)的時候就說了這個T類型無法保存,但是這里用橋避開了這個問題。利用模版函數(shù)的T做為DelegationImpl的實例化參數(shù),一切就這么簡單地解決了。

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

  接下來我們要討論一下這個橋式委托的性能消耗以及擴(kuò)展和局限性問題

#p#

  橋式委托的進(jìn)一步研究

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

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

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

  1. // 新版的橋式委托,可以接收類的成員函數(shù)作為響應(yīng)  
  2. struct DelegationInterface {   
  3. virtual ~DelegationInterface() {};  
  4. virtual void Run() = 0;  
  5. };  
  6.  
  7. templateclass T>  
  8. struct DelegationImpl : public DelegationInterface {  
  9. typedef void (T::* _pF_t)(); // 指向類T成員函數(shù)的指針類型  
  10.  
  11. DelegationImpl(T* _PP, _pF_t pF) :_P(_PP), _PF(pF) {}  
  12. virtual void Run() {  
  13. if(_P) { (_P->*_PF)(); } // 成員函數(shù)調(diào)用,很別扭的寫法(_P->*_PF)();  
  14. }  
  15.  
  16. T* _P; // Receiver類  
  17. _pF_t _PF; // 指向Receiver類的某個成員函數(shù)  
  18. };  
  19. struct Signal  
  20. {  
  21. DelegationInterface* _PI;  
  22. Signal() :_PI(NULL) {}  
  23. void operator() () { if(_PI) _PI->Run(); }  
  24.  
  25. // 新的ConnectSlot需要指定一個類以及這個類的某個成員函數(shù)  
  26. templateclass T>  
  27. void ConnectSlot(T& recv, void (T::* pF)()) { // pF這個參數(shù)真夠別扭的  
  28. _PI = new DelegationImpl<T>(&recv, pF);  
  29. }  
  30. }; 

  注意:ConnectSlot方法的pF參數(shù)類型非常復(fù)雜,也可以簡化如下,即把這個類型檢測推到DelegationImpl類去完成,而不在Connect這里進(jìn)行么?編譯器可以正確識別。對于模板來說,很多復(fù)雜的參數(shù)類型都可以用一個簡單的類型代替,不用關(guān)心細(xì)節(jié),就象上面用一個F代替void (T::*)()。有時候能改善可讀性,有時候象反。

  1. templateclass T, class F>  
  2. void ConnectSlot( T& recv, F pF ) {  
  3. PI_ = new DelegationImpl<T>(&recv,pF);  

  這個新版怎么用呢,很簡單的。比如你的MsgBox類有一個成員函數(shù)Show,你可以把這個作為響應(yīng)函數(shù):

  1. MsgBox box;  
  2. Socket x; // Socket還跟舊的版本一樣  
  3. x.OnRecv.ConnectSlot(box, &MsgBox::Show); 

  注意上面這里引用成員函數(shù)指針的寫法,一定不能寫成box.Show,呵呵,希望你還記得成員函數(shù)是屬于類公共的東西,不是某個實例的私有產(chǎn)品。大家不妨進(jìn)一步動一下腦筋,把新版的Signal和舊版的Signal結(jié)合一下,你就可以獲得一個功能超強(qiáng)的Delegation系統(tǒng)了。

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

#p#

  結(jié)論

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

  1. templateclass P1>  
  2. struct DelegationInterface { virtual void Run(P1 param) = 0; };  
  3. templateclass T, class P1>  
  4. struct DelegationImpl : public DelegationInterface<P1> {  
  5. ......  
  6. }  
  7. templateclass P1>  
  8. struct Signal {  
  9. DelegationInterface<P1> *_PI;  
  10. ......  

  以上、你會發(fā)現(xiàn)自己寫了太多橋了,當(dāng)然了,你可以繞路來實現(xiàn),比如用一個通用的打包參數(shù)來包裝多個參數(shù),用宏定義來處理各種情況,當(dāng)然也可以用預(yù)處理來實現(xiàn)——我這里要說的,同情一下QT吧,不要整天抱怨他的signal/slot體系需要預(yù)處理是在擴(kuò)展語言——設(shè)身處地地想一想,C++提供給我們的就這有這些了,一個小小的參數(shù)是我們這些signal/slot抹不去的傷痛。

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

【編輯推薦】

  1. 6.2.1 簡單的委托示例
  2. 0.5 反饋方法
  3. 程序員必看 c++筆試題匯總
  4. c++最基礎(chǔ)的20條規(guī)則
  5. c++編程常用工具
責(zé)任編輯:韓亞珊 來源: 天極網(wǎng)
相關(guān)推薦

2010-01-19 13:43:59

C++函數(shù)

2010-01-28 11:08:09

C++變量

2009-09-08 15:28:24

C#委托

2010-01-19 09:54:19

C++代碼

2023-12-18 11:15:03

2010-01-13 13:27:00

C++優(yōu)化

2023-12-13 10:51:49

C++函數(shù)模板編程

2010-02-01 13:08:46

C++函數(shù)指針C#托

2009-08-27 16:53:01

C#委托C#事件

2023-12-06 13:48:00

C++代碼

2010-02-03 17:42:33

C++模板參數(shù)

2010-02-06 16:59:19

C++ kmp算法模板

2010-01-26 13:55:07

C++標(biāo)準(zhǔn)模板庫

2009-08-20 18:11:08

C#異步委托

2022-09-22 10:22:36

C++編程語言代碼

2010-01-11 15:47:37

C++編譯

2009-08-18 10:54:17

C#事件和委托

2009-08-26 14:27:54

C#委托和事件

2009-10-09 09:07:40

C#委托和事件

2024-10-05 00:00:35

Action?C#Func?
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號