撰文:Don Clugston
翻譯:周翔
(接中篇)
委托(delegate)
和成員函數(shù)指針不同,你不難發(fā)現(xiàn)委托的用處。最重要的,使用委托可以很容易地實(shí)現(xiàn)一個(gè)Subject/Observer設(shè)計(jì)模式的改進(jìn)版[GoF, p. 293]。Observer(觀察者)模式顯然在GUI中有很多的應(yīng)用,但我發(fā)現(xiàn)它對(duì)應(yīng)用程序核心的設(shè)計(jì)也有很大的作用。委托也可用來(lái)實(shí)現(xiàn)策略(Strategy)[GoF, p. 315]和狀態(tài)(State)[GoF, p. 305]模式。
現(xiàn)在,我來(lái)說(shuō)明一個(gè)事實(shí),委托和成員函數(shù)指針相比并不僅僅是好用,而且比成員函數(shù)指針簡(jiǎn)單得多!既然所有的.NET語(yǔ)言都實(shí)現(xiàn)了委托,你可能會(huì)猜想如此高層的概念在匯編代碼中并不好實(shí)現(xiàn)。但事實(shí)并不是這樣:委托的實(shí)現(xiàn)確實(shí)是一個(gè)底層的概念,而且就像普通的函數(shù)調(diào)用一樣簡(jiǎn)單(并且很高效)。一個(gè)C++委托只需要包含一個(gè)this指針和一個(gè)簡(jiǎn)單的函數(shù)指針就夠了。當(dāng)你建立一個(gè)委托時(shí),你提供這個(gè)委托一個(gè)this指針,并向它指明需要調(diào)用哪一個(gè)函數(shù)。編譯器可以在建立委托時(shí)計(jì)算出調(diào)整this指針需要的偏移量。這樣在使用委托的時(shí)候,編譯器就什么事情都不用做了。這一點(diǎn)更好的是,編譯器可以在編譯時(shí)就可以完成全部這些工作,這樣的話,委托的處理對(duì)編譯器來(lái)說(shuō)可以說(shuō)是微不足道的工作了。在x86系統(tǒng)下將委托處理成的匯編代碼就應(yīng)該是這么簡(jiǎn)單:
mov ecx, [this]
call [pfunc]
但是,在標(biāo)準(zhǔn)C++中卻不能生成如此高效的代碼。 Borland為了解決委托的問(wèn)題在它的C++編譯器中加入了一個(gè)新的關(guān)鍵字(__closure),用來(lái)通過(guò)簡(jiǎn)潔的語(yǔ)法生成優(yōu)化的代碼。GNU編譯器也對(duì)語(yǔ)言進(jìn)行了擴(kuò)展,但和Borland的編譯器不兼容。如果你使用了這兩種語(yǔ)言擴(kuò)展中的一種,你就會(huì)限制自己只使用一個(gè)廠家的編譯器。而如果你仍然遵循標(biāo)準(zhǔn)C++的規(guī)則,你仍然可以實(shí)現(xiàn)委托,但實(shí)現(xiàn)的委托就不會(huì)是那么高效了。
有趣的是,在C#和其他.NET語(yǔ)言中,執(zhí)行一個(gè)委托的時(shí)間要比一個(gè)函數(shù)調(diào)用慢8倍(參見http://msdn.microsoft.com/library/en-us/dndotnet/html/fastmanagedcode.asp)。我猜測(cè)這可能是垃圾收集和.NET安全檢查的需要。最近,微軟將“統(tǒng)一事件模型(unified event model)”加入到Visual C++中,隨著這個(gè)模型的加入,增加了__event、 __raise、__hook、__unhook、event_source和event_receiver等一些關(guān)鍵字。坦白地說(shuō),我對(duì)加入的這些特性很反感,因?yàn)檫@是完全不符合標(biāo)準(zhǔn)的,這些語(yǔ)法是丑陋的,因?yàn)樗鼈兪惯@種C++不像C++,并且會(huì)生成一堆執(zhí)行效率極低的代碼。
解決這個(gè)問(wèn)題的推動(dòng)力:對(duì)高效委托(fast delegate)的迫切需求
使用標(biāo)準(zhǔn)C++實(shí)現(xiàn)委托有一個(gè)過(guò)度臃腫的癥狀。大多數(shù)的實(shí)現(xiàn)方法使用的是同一種思路。這些方法的基本觀點(diǎn)是將成員函數(shù)指針看成委托但這樣的指針只能被一個(gè)單獨(dú)的類使用。為了避免這種局限,你需要間接地使用另一種思路:你可以使用模版為每一個(gè)類建立一個(gè)“成員函數(shù)調(diào)用器(member function invoker)”。委托包含了this指針和一個(gè)指向調(diào)用器(invoker)的指針,并且需要在堆上為成員函數(shù)調(diào)用器分配空間。
對(duì)于這種方案已經(jīng)有很多種實(shí)現(xiàn),包括在CodeProject上的實(shí)現(xiàn)方案。各種實(shí)現(xiàn)在復(fù)雜性上、語(yǔ)法(比如,有的和C#的語(yǔ)法很接近)上、一般性上有所不同。最具權(quán)威的一個(gè)實(shí)現(xiàn)是boost::function。最近,它已經(jīng)被采用作為下一個(gè)發(fā)布的C++標(biāo)準(zhǔn)版本中的一部分[Sutter1]。希望它能夠被廣泛地使用。
就像傳統(tǒng)的委托實(shí)現(xiàn)方法一樣,我同樣發(fā)覺(jué)這種方法并不十分另人滿意。雖然它提供了大家所期望的功能,但是會(huì)混淆一個(gè)潛在的問(wèn)題:人們?nèi)狈?duì)一個(gè)語(yǔ)言的底層的構(gòu)造。 “成員函數(shù)調(diào)用器”的代碼對(duì)幾乎所有的類都是一樣的,在所有平臺(tái)上都出現(xiàn)這種情況是令人沮喪的。畢竟,堆被用上了。但在一些應(yīng)用場(chǎng)合下,這種新的方法仍然無(wú)法被接受。
我做的一個(gè)項(xiàng)目是離散事件模擬器,它的核心是一個(gè)事件調(diào)度程序,用來(lái)調(diào)用被模擬的對(duì)象的成員函數(shù)。大多數(shù)成員函數(shù)非常簡(jiǎn)單:它們只改變對(duì)象的內(nèi)部狀態(tài),有時(shí)在事件隊(duì)列(event queue)中添加將來(lái)要發(fā)生的事件,在這種情況下最適合使用委托。但是,每一個(gè)委托只被調(diào)用(invoked)一次。一開始,我使用了boost::function,但我發(fā)現(xiàn)程序運(yùn)行時(shí),給委托所分配的內(nèi)存空間占用了整個(gè)程序空間的三分之一還要多!“我要真正的委托!”我在內(nèi)心呼喊著,“真正的委托只需要僅僅兩行匯編指令啊!”
我并不能總是能夠得到我想要的,但后來(lái)我很幸運(yùn)。我在這兒展示的代碼(代碼下載鏈接見譯者注)幾乎在所有編譯環(huán)境中都產(chǎn)生了優(yōu)化的匯編代碼。最重要的是,調(diào)用一個(gè)含有單個(gè)目標(biāo)的委托(single-target delegate)的速度幾乎同調(diào)用一個(gè)普通函數(shù)一樣快。實(shí)現(xiàn)這樣的代碼并沒(méi)有用到什么高深的東西,唯一的遺憾就是,為了實(shí)現(xiàn)目標(biāo),我的代碼和標(biāo)準(zhǔn)C++的規(guī)則有些偏離。我使用了一些有關(guān)成員函數(shù)指針的未公開知識(shí)才使它能夠這樣工作。如果你很細(xì)心,而且不在意在少數(shù)情況下的一些編譯器相關(guān)(compiler-specific)的代碼,那么高性能的委托機(jī)制在任何C++編譯器下都是可行的。
訣竅:將任何類型的成員函數(shù)指針轉(zhuǎn)化為一個(gè)標(biāo)準(zhǔn)的形式
我的代碼的核心是一個(gè)能夠?qū)⑷魏晤惖闹羔樅腿魏纬蓡T函數(shù)指針?lè)謩e轉(zhuǎn)換為一個(gè)通用類的指針和一個(gè)通用成員函數(shù)的指針的類。由于C++沒(méi)有“通用成員函數(shù)(generic member function)”的類型,所以我把所有類型的成員函數(shù)都轉(zhuǎn)化為一個(gè)在代碼中未定義的CGenericClass類的成員函數(shù)。
大多數(shù)編譯器對(duì)所有的成員函數(shù)指針平等地對(duì)待,不管他們屬于哪個(gè)類。所以對(duì)這些編譯器來(lái)說(shuō),可以使用reinterpret_cast將一個(gè)特定的成員函數(shù)指針轉(zhuǎn)化為一個(gè)通用成員函數(shù)指針。事實(shí)上,假如編譯器不可以,那么這個(gè)編譯器是不符合標(biāo)準(zhǔn)的。對(duì)于一些接近標(biāo)準(zhǔn)(almost-compliant)的編譯器,比如Digital Mars,成員函數(shù)指針的reinterpret_cast轉(zhuǎn)換一般會(huì)涉及到一些額外的特殊代碼,當(dāng)進(jìn)行轉(zhuǎn)化的成員函數(shù)的類之間沒(méi)有任何關(guān)聯(lián)時(shí),編譯器會(huì)出錯(cuò)。對(duì)這些編譯器,我們使用一個(gè)名為horrible_cast的內(nèi)聯(lián)函數(shù)(在函數(shù)中使用了一個(gè)union來(lái)避免C++的類型檢查)。使用這種方法看來(lái)是不可避免的boost::function也用到了這種方法。
對(duì)于其他的一些編譯器(如Visual C++, Intel C++和Borland C++),我們必須將多重(multiple-)繼承和虛擬(virtual-)繼承類的成員函數(shù)指針轉(zhuǎn)化為單一(single-)繼承類的函數(shù)指針。為了實(shí)現(xiàn)這個(gè)目的,我巧妙地使用了模板并利用了一個(gè)奇妙的戲法。注意,這個(gè)戲法的使用是因?yàn)檫@些編譯器并不是完全符合標(biāo)準(zhǔn)的,但是使用這個(gè)戲法得到了回報(bào):它使這些編譯器產(chǎn)生了優(yōu)化的代碼。
既然我們知道編譯器是怎樣在內(nèi)部存儲(chǔ)成員函數(shù)指針的,并且我們知道在問(wèn)題中應(yīng)該怎樣為成員函數(shù)指針調(diào)整this指針,我們的代碼在設(shè)置委托時(shí)可以自己調(diào)整this指針。對(duì)單一繼承類的函數(shù)指針,則不需要進(jìn)行調(diào)整;對(duì)多重繼承,則只需要一次加法就可完成調(diào)整;對(duì)虛擬繼承...就有些麻煩了。但是這樣做是管用的,并且在大多數(shù)情況下,所有的工作都在編譯時(shí)完成!
這是最后一個(gè)訣竅。我們?cè)鯓訁^(qū)分不同的繼承類型?并沒(méi)有官方的方法來(lái)讓我們區(qū)分一個(gè)類是多重繼承的還是其他類型的繼承。但是有一種巧妙的方法,你可以查看我在前面給出了一個(gè)列表(見中篇)——對(duì)MSVC,每種繼承方式產(chǎn)生的成員函數(shù)指針的大小是不同的。所以,我們可以基于成員函數(shù)指針的大小使用模版!比如對(duì)多重繼承類型來(lái)說(shuō),這只是個(gè)簡(jiǎn)單的計(jì)算。而在確定unknown_inheritance(16字節(jié))類型的時(shí)候,也會(huì)采用類似的計(jì)算方法。
對(duì)于微軟和英特爾的編譯器中采用不標(biāo)準(zhǔn)12字節(jié)的虛擬繼承類型的指針的情況,我引發(fā)了一個(gè)編譯時(shí)錯(cuò)誤(compile-time error),因?yàn)樾枰粋€(gè)特定的運(yùn)行環(huán)境(workaround)。如果你在MSVC中使用虛擬繼承,要在聲明類之前使用FASTDELEGATEDECLARE宏。而這個(gè)類必須使用unknown_inheritance(未知繼承類型)指針(這相當(dāng)于一個(gè)假定的__unknown_inheritance關(guān)鍵字)。例如:
FASTDELEGATEDECLARE(CDerivedClass)
class CDerivedClass : virtual public CBaseClass1, virtual public CBaseClass2 {
// : (etc)
};
這個(gè)宏和一些常數(shù)的聲明是在一個(gè)隱藏的命名空間中實(shí)現(xiàn)的,這樣在其他編譯器中使用時(shí)也是安全的。MSVC(7.0或更新版本)的另一種方法是在工程中使用/vmg編譯器選項(xiàng)。而Inter的編譯器對(duì)/vmg編譯器選項(xiàng)不起作用,所以你必須在虛擬繼承類中使用宏。我的這個(gè)代碼是因?yàn)榫幾g器的bug才可以正確運(yùn)行,你可以查看代碼來(lái)了解更多細(xì)節(jié)。而在遵從標(biāo)準(zhǔn)的編譯器中不需要注意這么多,況且在任何情況下都不會(huì)妨礙FASTDELEGATEDECLARE宏的使用。
一旦你將類的對(duì)象指針和成員函數(shù)指針轉(zhuǎn)化為標(biāo)準(zhǔn)形式,實(shí)現(xiàn)單一目標(biāo)的委托(single-target delegate)就比較容易了(雖然做起來(lái)感覺(jué)冗長(zhǎng)乏味)。你只要為每一種具有不同參數(shù)的函數(shù)制作相應(yīng)的模板類就行了。實(shí)現(xiàn)其他類型的委托的代碼也大都與此相似,只是對(duì)參數(shù)稍做修改罷了。
這種用非標(biāo)準(zhǔn)方式轉(zhuǎn)換實(shí)現(xiàn)的委托還有一個(gè)好處,就是委托對(duì)象之間可以用等式比較。目前實(shí)現(xiàn)的大多數(shù)委托無(wú)法做到這一點(diǎn),這使這些委托不能勝任一些特定的任務(wù),比如實(shí)現(xiàn)多播委托(multi-cast delegates) [Sutter3]。
靜態(tài)函數(shù)作為委托目標(biāo)(delegate target)
理論上,一個(gè)簡(jiǎn)單的非成員函數(shù)(non-member function),或者一個(gè)靜態(tài)成員函數(shù)(static member function)可以被作為委托目標(biāo)(delegate target)。這可以通過(guò)將靜態(tài)函數(shù)轉(zhuǎn)換為一個(gè)成員函數(shù)來(lái)實(shí)現(xiàn)。我有兩種方法實(shí)現(xiàn)這一點(diǎn),兩種方法都是通過(guò)使委托指向調(diào)用這個(gè)靜態(tài)函數(shù)的“調(diào)用器(invoker)”的成員函數(shù)的方法來(lái)實(shí)現(xiàn)的。
第一種方法使用了一個(gè)邪惡的方法(evil method)。你可以存儲(chǔ)函數(shù)指針而不是this指針,這樣當(dāng)調(diào)用“調(diào)用器”的函數(shù)時(shí),它將this指針轉(zhuǎn)化為一個(gè)靜態(tài)函數(shù)指針,并調(diào)用這個(gè)靜態(tài)函數(shù)。問(wèn)題是這只是一個(gè)戲法,它需要在代碼指針和數(shù)據(jù)指針之間進(jìn)行轉(zhuǎn)換。在一個(gè)系統(tǒng)中代碼指針的大小比數(shù)據(jù)指針大時(shí)(比如DOS下的編譯器使用medium內(nèi)存模式時(shí)),這個(gè)方法就不管用了。它在目前我知道的所有32位和64位處理器上是管用的。但是因?yàn)檫@種方法還是不太好,所以仍需要改進(jìn)。
另一種是一個(gè)比較安全的方法(safe method),它是將函數(shù)指針作為委托的一個(gè)附加成員。委托指向自己的成員函數(shù)。當(dāng)委托被復(fù)制的時(shí)候,這些自引用(self-reference)必須被轉(zhuǎn)換,而且使“=”和“==”運(yùn)算符的操作變得復(fù)雜。這使委托的大小增至4個(gè)字節(jié),并增加了代碼的復(fù)雜性,但這并不影響委托的調(diào)用速度。
我已經(jīng)實(shí)現(xiàn)了上述兩種方法,兩者都有各自的優(yōu)點(diǎn):安全的方法保證了運(yùn)行的可靠性,而邪惡的方法在支持委托的編譯器下也可能會(huì)產(chǎn)生與此相同的匯編代碼。此外,安全的方法可避免我以前討論的在MSVC中使用多重繼承和虛擬繼承時(shí)所出現(xiàn)的問(wèn)題。我在代碼中給出的是“安全的方法”的代碼,但是在我給出的代碼中“邪惡的方法”會(huì)通過(guò)下面的代碼生效:
#define (FASTDELEGATE_USESTATICFUNCTIONHACK)
多目標(biāo)委托(multiple-target delegate)及其擴(kuò)展
使用委托的人可能會(huì)想使委托調(diào)用多個(gè)目標(biāo)函數(shù),這就是多目標(biāo)委托(multiple-target delegate),也稱作多播委托(multi-cast delegate)。實(shí)現(xiàn)這種委托不會(huì)降低單一目標(biāo)委托(single-target delegate)的調(diào)用效率,這在現(xiàn)實(shí)中是可行的。你只需要為一個(gè)委托的第二個(gè)目標(biāo)和后來(lái)的更多目標(biāo)在堆上分配空間就可以了,這意味著需要在委托類中添加一個(gè)數(shù)據(jù)指針,用來(lái)指向由該委托的目標(biāo)函數(shù)組成的單鏈表的頭部節(jié)點(diǎn)。如果委托只有一個(gè)目標(biāo)函數(shù),將這個(gè)目標(biāo)像以前介紹的方法一樣保存在委托中就行了。如果一個(gè)委托有多個(gè)目標(biāo)函數(shù),那么這些目標(biāo)都保存在空間動(dòng)態(tài)分配的鏈表中,如果要調(diào)用函數(shù),委托使用一個(gè)指針指向一個(gè)鏈表中的目標(biāo)(成員函數(shù)指針)。這樣的話,如果委托中只有一個(gè)目標(biāo),函數(shù)調(diào)用存儲(chǔ)單元的個(gè)數(shù)為1;如果有n(n>0)個(gè)目標(biāo),則函數(shù)調(diào)用存儲(chǔ)單元的個(gè)數(shù)為n+1(因?yàn)檫@時(shí)函數(shù)指針保存在鏈表中,會(huì)多出一個(gè)鏈表頭,所以要再加一——譯者注),我認(rèn)為這樣做最合理。
由多播委托引出了一些問(wèn)題。怎樣處理返回值?(是將所有返回值類型捆綁在一起,還是忽略一部分?)如果把同一個(gè)目標(biāo)在一個(gè)委托中添加了兩次那會(huì)發(fā)生什么?(是調(diào)用同一個(gè)目標(biāo)兩次,還是只調(diào)用一次,還是作為一個(gè)錯(cuò)誤處理?)如果你想在委托中刪除一個(gè)不在其中的目標(biāo)應(yīng)該怎么辦?(是不管它,還是拋出一個(gè)異常?)
最重要的問(wèn)題是在使用委托時(shí)會(huì)出現(xiàn)無(wú)限循環(huán)的情況,比如,A委托調(diào)用一段代碼,而在這段代碼中調(diào)用B委托,而在B委托調(diào)用的一段代碼中又會(huì)調(diào)用A委托。很多事件(event)和信號(hào)跟蹤(signal-slot)系統(tǒng)會(huì)有一定的方案來(lái)處理這種問(wèn)題。
為了結(jié)束我的這篇文章,我的多播委托的實(shí)現(xiàn)方案就需要大家等待了。這可以借鑒其他實(shí)現(xiàn)中的方法——允許非空返回類型,允許類型的隱式轉(zhuǎn)換,并使用更簡(jiǎn)捷的語(yǔ)法結(jié)構(gòu)。如果我有足夠的興趣我會(huì)把代碼寫出來(lái)。如果能把我實(shí)現(xiàn)的委托和目前流行的某一個(gè)事件處理系統(tǒng)結(jié)合起來(lái)那會(huì)是最好不過(guò)的事情了(有自愿者嗎?)。
本文代碼的使用
原代碼包括了FastDelegate的實(shí)現(xiàn)(FastDelegate.h)和一個(gè)demo .cpp的文件用來(lái)展示使用FastDelegate的語(yǔ)法。對(duì)于使用MSVC的讀者,你可以建立一個(gè)空的控制臺(tái)應(yīng)用程序(Console Application)的工程,再把這兩個(gè)文件添加進(jìn)去就好了,對(duì)于GNU的使用者,在命令行輸入“gcc demo.cpp”就可以了。
FastDelegate可以在任何參數(shù)組合下運(yùn)行,我建議你在盡可能多的編譯器下嘗試,你在聲明委托的時(shí)候必須指明參數(shù)的個(gè)數(shù)。在這個(gè)程序中最多可以使用8個(gè)參數(shù),若想進(jìn)行擴(kuò)充也是很容易的。代碼使用了fastdelegate命名空間,在fastdelegate命名空間中有一個(gè)名為detail的內(nèi)部命名空間。
Fastdelegate使用構(gòu)造函數(shù)或bind()可以綁定一個(gè)成員函數(shù)或一個(gè)靜態(tài)(全局)函數(shù),在默認(rèn)情況下,綁定的值為0(空函數(shù))。可以使用“!”操作符判定它是一個(gè)空值。
不像用其他方法實(shí)現(xiàn)的委托,這個(gè)委托支持等式運(yùn)算符(==, !=)。
下面是FastDelegateDemo.cpp的節(jié)選,它展示了大多數(shù)允許的操作。CBaseClass是CDerivedClass的虛基類。你可以根據(jù)這個(gè)代碼寫出更精彩的代碼,下面的代碼只是說(shuō)明使用FastDelegate的語(yǔ)法:
using namespace fastdelegate;
int main(void)
{
printf("-- FastDelegate demo --\nA no-parameter
delegate is declared using FastDelegate0\n\n");
FastDelegate0 noparameterdelegate(&SimpleVoidFunction);
noparameterdelegate();
//調(diào)用委托,這一句調(diào)用SimpleVoidFunction()
printf("\n-- Examples using two-parameter delegates (int, char *) --\n\n");
typedef FastDelegate2 MyDelegate;
MyDelegate funclist[12]; // 委托初始化,其目標(biāo)為空
CBaseClass a("Base A");
CBaseClass b("Base B");
CDerivedClass d;
CDerivedClass c;
// 綁定一個(gè)成員函數(shù)
funclist[0].bind(&a, &CBaseClass::SimpleMemberFunction);
//你也可以綁定一個(gè)靜態(tài)(全局)函數(shù)
funclist[1].bind(&SimpleStaticFunction);
//綁定靜態(tài)成員函數(shù)
funclist[2].bind(&CBaseClass::StaticMemberFunction);
// 綁定const型的成員函數(shù)
funclist[3].bind(&a, &CBaseClass::ConstMemberFunction);
// 綁定虛擬成員函數(shù)
funclist[4].bind(&b, &CBaseClass::SimpleVirtualFunction);
// 你可以使用”=”來(lái)賦值
funclist[5] = MyDelegate(&CBaseClass::StaticMemberFunction);
funclist[6].bind(&d, &CBaseClass::SimpleVirtualFunction);
//最麻煩的情況是綁定一個(gè)抽象虛擬函數(shù)(abstract virtual function)
funclist[7].bind(&c, &CDerivedClass::SimpleDerivedFunction);
funclist[8].bind(&c, &COtherClass::TrickyVirtualFunction);
funclist[9] = MakeDelegate(&c, &CDerivedClass::SimpleDerivedFunction);
// 你也可以使用構(gòu)造函數(shù)來(lái)綁定
MyDelegate dg(&b, &CBaseClass::SimpleVirtualFunction);
char *msg = "Looking for equal delegate";
for (int i=0; i<12; i++) {
printf("%d :", i);
// 可以使用”==”
if (funclist[i]==dg) { msg = "Found equal delegate"; };
//可以使用”!”來(lái)判應(yīng)一個(gè)空委托
if (!funclist[i]) {
printf("Delegate is empty\n");
} else {
// 調(diào)用生成的經(jīng)過(guò)優(yōu)化的匯編代碼
funclist[i](i, msg);
};
}
};
因?yàn)槲业拇a利用了C++標(biāo)準(zhǔn)中沒(méi)有定義的行為,所以我很小心地在很多編譯器中做了測(cè)試。具有諷刺意味的是,它比許多所謂標(biāo)準(zhǔn)的代碼更具有可移植性,因?yàn)閹缀跛械木幾g器都不是完全符合標(biāo)準(zhǔn)的。目前,核心代碼已成功通過(guò)了下列編譯器的測(cè)試:
- Microsoft Visual C++ 6.0, 7.0 (.NET) and 7.1 (.NET 2003) (including /clr 'managed C++'),
- GNU G++ 3.2 (MingW binaries),
- Borland C++ Builder 5.5.1,
- Digital Mars C++ 8.38 (x86, both 32-bit and 16-bit),
- Intel C++ for Windows 8.0,
- Metroworks CodeWarrior for Windows 9.1 (in both C++ and EC++ modes)
對(duì)于Comeau C++ 4.3 (x86, SPARC, Alpha, Macintosh),能夠成功通過(guò)編譯,但不能鏈接和運(yùn)行。對(duì)于Intel C++ 8.0 for Itanium能夠成功通過(guò)編譯和鏈接,但不能運(yùn)行。
此外,我已對(duì)代碼在MSVC 1.5 和4.0,Open Watcom WCL 1.2上的運(yùn)行情況進(jìn)行了測(cè)試,由于這些編譯器不支持成員函數(shù)模版,所以對(duì)這些編譯器,代碼不能編譯成功。對(duì)于嵌入式系統(tǒng)不支持模版的限制,需要對(duì)代碼進(jìn)行大范圍的修改。(這一段是在剛剛更新的原文中添加的——譯者注)
而最終的FastDelegate并沒(méi)有進(jìn)行全面地測(cè)試,一個(gè)原因是,我有一些使用的編譯器的評(píng)估版過(guò)期了,另一個(gè)原因是——我的女兒出生了!如果有足夠的興趣,我會(huì)讓代碼在更多編譯器中通過(guò)測(cè)試。(這一段在剛剛更新的原文中被刪去了,因?yàn)樽髡吣壳皫缀跬瓿闪巳繙y(cè)試。——譯者注)
總結(jié)
為了解釋一小段代碼,我就得為這個(gè)語(yǔ)言中具有爭(zhēng)議的一部分寫這么一篇長(zhǎng)長(zhǎng)的指南。為了兩行匯編代碼,就要做如此麻煩的工作。唉~!
我希望我已經(jīng)澄清了有關(guān)成員函數(shù)指針和委托的誤解。我們可以看到為了實(shí)現(xiàn)成員函數(shù)指針,各種編譯器有著千差萬(wàn)別的方法。我們還可以看到,與流行的觀點(diǎn)不同,委托并不復(fù)雜,并不是高層結(jié)構(gòu),事實(shí)上它很簡(jiǎn)單。我希望它能夠成為這個(gè)語(yǔ)言(標(biāo)準(zhǔn)C++)中的一部分,而且我們有理由相信目前已被一些編譯器支持的委托,在不久的將來(lái)會(huì)加入到標(biāo)準(zhǔn)C++的新的版本中(去游說(shuō)標(biāo)準(zhǔn)委員會(huì)!)。
據(jù)我所知,以前實(shí)現(xiàn)的委托都沒(méi)有像我在這里為大家展示的FastDelegate一樣有如此高的性能。我希望我的代碼能對(duì)你有幫助。如果我有足夠的興趣,我會(huì)對(duì)代碼進(jìn)行擴(kuò)展,從而支持多播委托(multi-cast delegate)以及更多類型的委托。我在CodeProject上學(xué)到了很多,并且這是我第一次為之做出的貢獻(xiàn)。
參考文獻(xiàn)
[GoF] "Design Patterns: Elements of Reusable Object-Oriented Software", E. Gamma, R. Helm, R. Johnson, and J. Vlissides.
I've looked at dozens of websites while researching this article. Here are a few of the most interesting ones:
我在寫這篇文章時(shí)查看了很多站點(diǎn),下面只是最有趣的一些站點(diǎn):
[Boost] Delegates can be implemented with a combination of boost::function and boost::bind. Boost::signals is one of the most sophisticated event/messaging system available. Most of the boost libraries require a highly standards-conforming compiler.(http://www.boost.org/)
[Loki] Loki provides 'functors' which are delegates with bindable parameters. They are very similar to boost::function. It's likely that Loki will eventually merge with boost. (http://sourceforge.net/projects/loki-lib)
[Qt] The Qt library includes a Signal/Slot mechanism (i.e., delegates). For this to work, you have to run a special preprocessor on your code before compiling. Performance is very poor, but it works on compilers with very poor template support.(http://doc.trolltech.com/3.0/signalsandslots.html)
[Libsigc++] An event system based on Qt's. It avoids the Qt's special preprocessor, but requires that every target be derived from a base object class (using virtual inheritance - yuck!). (http://libsigc.sourceforge.net/)
[Hickey]. An old (1994) delegate implementation that avoids memory allocations. Assumes that all pointer-to-member functions are the same size, so it doesn't work on MSVC. There's a helpful discussion of the code here. (http://www.tutok.sk/fastgl/callback.html)
[Haendal]. A website dedicated to function pointers?! Not much detail about member function pointers though.(http://www.function-pointer.org/)
[Sutter1] Generalized function pointers: a discussion of how boost::function has been accepted into the new C++ standard.(http://www.cuj.com/documents/s=8464/cujcexp0308sutter/)
[Sutter2] Generalizing the Observer pattern (essentially, multicast delegates) using std::tr1::function. Discusses the limitations of the failure of boost::function to provide operator ==.
(http://www.cuj.com/documents/s=8840/cujexp0309sutter)
[Sutter3] Herb Sutter's Guru of the Week article on generic callbacks. (http://www.gotw.ca/gotw/083.htm)
關(guān)于作者Don Clugston
我在澳大利亞的high-tech startup工作,是一個(gè)物理學(xué)家兼軟件工程師。目前從事將太陽(yáng)航空艙的硅質(zhì)晶體玻璃(CSG)薄膜向市場(chǎng)推廣的工作。我從事有關(guān)太陽(yáng)的(solar)研究,平時(shí)喜歡做一些軟件(用作數(shù)學(xué)模型、設(shè)備控制、離散事件觸發(fā)器和圖象處理等),我最近喜歡使用STL和WTL寫代碼。我非常懷念過(guò)去的光榮歲月:)而最重要的,我有一個(gè)非常可愛(ài)的兒子(2002年5月出生)和一個(gè)非常年輕的小姐(2004年5月出生)。
“黑暗不會(huì)戰(zhàn)勝陽(yáng)光,陽(yáng)光終究會(huì)照亮黑暗。”
譯者注
由于本文剛發(fā)表不久,作者隨時(shí)都有可能對(duì)文章或代碼進(jìn)行更新,若要瀏覽作者對(duì)本文的最新內(nèi)容,請(qǐng)?jiān)L問(wèn):
http://www.codeproject.com/cpp/FastDelegate.asp
點(diǎn)擊以下鏈接下載FastDelegate的源代碼:
http://www.codeproject.com/cpp/FastDelegate/FastDelegate_src.zip
轉(zhuǎn)自:http://dev.csdn.net/htmls/30/30159.html
附文中提及 codeproject 文章中包含的源代碼:
Download source files:FastDelegate_src.zip
Download macro files used to develop this library:FastDelegate_hopter.zip