麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

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

2019-11-17 05:07:53
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

  摘要:本文簡(jiǎn)單介紹并比較了用于實(shí)現(xiàn)消息反饋的幾種常見(jiàn)技術(shù),其中具體介紹了利用C++模板技術(shù)來(lái)實(shí)現(xiàn)類型安全的委托的要點(diǎn)和限制,可以作為理解qt/gtk+等UI庫(kù)的信號(hào)反饋機(jī)制的入門文章。

  正文:我寫過(guò)不少C++程序,寫過(guò)庫(kù)也寫過(guò)客戶程序。一般庫(kù)都會(huì)提供一些好用的類供客戶程序使用,不少庫(kù)還可以讓客戶程序響應(yīng)庫(kù)內(nèi)的某些事件。比如MFC/ATL/VCL提供消息響應(yīng),很多ActiveX提供自定義消息響應(yīng),甚至是系統(tǒng)底層的中斷調(diào)用都可以列入這個(gè)范疇。然而,正是以上這些“反向”的調(diào)用讓我覺(jué)得很煩惱。

  繼續(xù)+多態(tài)

  乍一看是理所當(dāng)然的選擇,庫(kù)中的類把響應(yīng)處理函數(shù)設(shè)置為虛函數(shù),客戶程序可以繼續(xù)這個(gè)類并且重載響應(yīng)函數(shù)。以某個(gè)Socket類為例,可以提供一個(gè)OnRecv函數(shù)用來(lái)響應(yīng)網(wǎng)絡(luò)數(shù)據(jù)包到達(dá)的處理??蛻舫绦蛑恍枰剌dOnRecv并進(jìn)行自己的處理就可以了。

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

  點(diǎn)評(píng):為了使用Socket就必須繼續(xù)Socket,這可以說(shuō)是Socket的設(shè)計(jì)的問(wèn)題。假如需要實(shí)現(xiàn)類似的功能的話,可以寫成如下,雖然和繼續(xù) Socket 沒(méi)有多少本質(zhì)的差別,不過(guò)確實(shí)把消息處理類和Socket的實(shí)現(xiàn)扯開了。:

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簡(jiǎn)單明確的表達(dá)方法。丟開繼續(xù),我們還有什么把戲?我不禁想起了c時(shí)代的回調(diào)函數(shù)……

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

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

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

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

  委托是什么呢?這個(gè)名詞似乎是時(shí)尚的代名詞,我仿佛看到學(xué)java/c#的兄弟們?cè)诔靶ξ覀兊穆浜蟆鋵?shí),property不也可以算是一種委托嗎?說(shuō)白了不就是智能指針么?


  我覺(jué)得委托最本質(zhì)的是提供一種類型安全的動(dòng)態(tài)消息響應(yīng)轉(zhuǎn)移機(jī)制。

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

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

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

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

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

  這個(gè)方法可行么?Yes!

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

  請(qǐng)答應(yīng)我杜撰一個(gè)名詞:橋式委托(Bridge Delegation)

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

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

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

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

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

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

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

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

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

  我們來(lái)看看如何實(shí)現(xiàn)這個(gè)橋:首先是一個(gè)抽象類

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

  再看看我們的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來(lái)呼叫響應(yīng)函數(shù)。而完成這一切連接操作的正是這個(gè)奇妙的ConnectSlot的函數(shù)。對(duì)了!上次討論模版函數(shù)的時(shí)候就說(shuō)了這個(gè)T類型無(wú)法保存,但是這里用橋避開了這個(gè)問(wèn)題。利用模版函數(shù)的T做為DelegationImpl的實(shí)例化參數(shù),一切就這么簡(jiǎn)單地解決了~

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

  接下來(lái)我們要討論一下這個(gè)橋式委托的性能消耗以及擴(kuò)展和局限性問(wèn)題 橋式委托的進(jìn)一步研究

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

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

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

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

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

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

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

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

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

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

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

  結(jié)論

  我們關(guān)于橋式委托的討論接近尾聲了,大家也許已經(jīng)發(fā)現(xiàn)了一個(gè)巨大的問(wèn)題:上面的橋式委托無(wú)法給相應(yīng)操作傳遞參數(shù)?。。∈堑?,這是一個(gè)巨大的矛盾——你必須自己實(shí)現(xiàn)帶一個(gè)參數(shù)的橋、自己實(shí)現(xiàn)帶2個(gè)參數(shù)的橋……就像stl的functor一樣,你無(wú)法做到參數(shù)通用處理,必須區(qū)分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++語(yǔ)法這么不給面子……當(dāng)然了,你可以繞路來(lái)實(shí)現(xiàn),比如用一個(gè)通用的打包參數(shù)來(lái)包裝多個(gè)參數(shù),用宏定義來(lái)處理各種情況,當(dāng)然也可以用預(yù)處理來(lái)實(shí)現(xiàn)——我這里要說(shuō)的,同情一下QT吧,不要整天抱怨他的signal/slot體系需要預(yù)處理是在擴(kuò)展語(yǔ)言——設(shè)身處地地想一想,C++提供給我們的就這有這些了,一個(gè)小小的參數(shù)是我們這些signal/slot抹不去的傷痛。


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

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 热久久91 | 亚洲一区成人在线 | 亚洲黑人在线观看 | 国产一区二区精品在线观看 | 超级av在线 | 秋霞a级毛片在线看 | 久久精品欧美一区二区三区不卡 | 最新欧美精品一区二区三区 | 久草在线资源观看 | 99国产精品欲a | 精品国产欧美一区二区 | 欧美a在线观看 | 久久久久久艹 | 久久成年网站 | 极品五月天 | 九九视频精品在线 | 亚洲国产精品一区二区久久 | 欧日韩在线 | www成人在线观看 | 看全色黄大色黄大片女图片 | 久久精品一二三区白丝高潮 | 91九色视频在线观看 | 综合激情网 | 在线看国产视频 | 男女污视频在线观看 | www.9191.com| 久久99精品久久 | 精品国产一区二 | 黄片毛片一级 | 国产女厕一区二区三区在线视 | 国产91成人 | 美国av在线免费观看 | 久久免费视频8 | 中文字幕综合在线观看 | 久久综合福利 | 手机免费看一级片 | 国产午夜精品理论片a级探花 | 无遮挡一级毛片视频 | 久久久久亚洲视频 | 一级免费特黄视频 | 青草av.久久免费一区 |