• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            成員函數(shù)指針與高性能的C++委托

            Member Function Pointers and the Fastest Possible C++ Delegates

             

            撰文:Don Clugston

            翻譯:周翔

             

            引子

            標(biāo)準(zhǔn)C++中沒(méi)有真正的面向?qū)ο蟮暮瘮?shù)指針。這一點(diǎn)對(duì)C++來(lái)說(shuō)是不幸的,因?yàn)槊嫦驅(qū)ο蟮闹羔槪ㄒ步凶?#8220;閉包(closure)”或“委托(delegate)”)在一些語(yǔ)言中已經(jīng)證明了它寶貴的價(jià)值。在Delphi (Object Pascal)中,面向?qū)ο蟮暮瘮?shù)指針是Borland可視化組建庫(kù)(VCL,Visual Component Library)的基礎(chǔ)。而在目前,C#使“委托”的概念日趨流行,這也正顯示出C#這種語(yǔ)言的成功。在很多應(yīng)用程序中,“委托”簡(jiǎn)化了松耦合對(duì)象的設(shè)計(jì)模式[GoF]。這種特性無(wú)疑在標(biāo)準(zhǔn)C++中也會(huì)產(chǎn)生很大的作用。

            很遺憾,C++中沒(méi)有“委托”,它只提供了成員函數(shù)指針(member function pointers。很多程序員從沒(méi)有用過(guò)函數(shù)指針,這是有特定的原因的。因?yàn)楹瘮?shù)指針自身有很多奇怪的語(yǔ)法規(guī)則(比如“->*”和“.*”操作符),而且很難找到它們的準(zhǔn)確含義,并且你會(huì)找到更好的辦法以避免使用函數(shù)指針。更具有諷刺意味的是:事實(shí)上,編譯器的編寫(xiě)者如果實(shí)現(xiàn)“委托”的話會(huì)比他費(fèi)勁地實(shí)現(xiàn)成員函數(shù)指針要容易地多!

            在這篇文章中,我要揭開(kāi)成員函數(shù)指針那“神秘的蓋子”。在扼要地重述成員函數(shù)指針的語(yǔ)法和特性之后,我會(huì)向讀者解釋成員函數(shù)指針在一些常用的編譯器中是怎樣實(shí)現(xiàn)的,然后我會(huì)向大家展示編譯器怎樣有效地實(shí)現(xiàn)“委托”。最后我會(huì)利用這些精深的知識(shí)向你展示在C++編譯器上實(shí)現(xiàn)優(yōu)化而可靠的“委托”的技術(shù)。比如,在Visual C++(6.0, .NET, and .NET 2003)中對(duì)單一目標(biāo)委托(single-target delegate)的調(diào)用,編譯器僅僅生成兩行匯編代碼!

             

            函數(shù)指針

            下面我們復(fù)習(xí)一下函數(shù)指針。在C和C++語(yǔ)言中,一個(gè)命名為my_func_ptr的函數(shù)指針指向一個(gè)以一個(gè)int和一個(gè)char*為參數(shù)的函數(shù),這個(gè)函數(shù)返回一個(gè)浮點(diǎn)值,聲明如下:

            float (*my_func_ptr)(int, char *);

            為了便于理解,我強(qiáng)烈推薦你使用typedef關(guān)鍵字。如果不這樣的話,當(dāng)函數(shù)指針作為一個(gè)函數(shù)的參數(shù)傳遞的時(shí)候,程序會(huì)變得晦澀難懂。這樣的話,聲明應(yīng)如下所示:

            typedef float (*MyFuncPtrType)(int, char *);

            MyFuncPtrType my_func_ptr;

            應(yīng)注意,對(duì)每一個(gè)函數(shù)的參數(shù)組合,函數(shù)指針的類(lèi)型應(yīng)該是不同的。在Microsoft Visual C++(以下稱(chēng)MSVC)中,對(duì)三種不同的調(diào)用方式有不同的類(lèi)型:__cdecl, __stdcall, 和__fastcall。如果你的函數(shù)指針指向一個(gè)型如float some_func(int, char *)的函數(shù),這樣做就可以了:

            my_func_ptr = some_func;

            當(dāng)你想調(diào)用它所指向的函數(shù)時(shí),你可以這樣寫(xiě):

            (*my_func_ptr)(7, "Arbitrary String");

            你可以將一種類(lèi)型的函數(shù)指針轉(zhuǎn)換成另一種函數(shù)指針類(lèi)型,但你不可以將一個(gè)函數(shù)指針指向一個(gè)void *型的數(shù)據(jù)指針。其他的轉(zhuǎn)換操作就不用詳敘了。一個(gè)函數(shù)指針可以被設(shè)置為0來(lái)表明它是一個(gè)空指針。所有的比較運(yùn)算符(==, !=, <, >, <=, >=)都可以使用,可以使用“==

            在C語(yǔ)言中,函數(shù)指針通常用來(lái)像qsort一樣將函數(shù)作為參數(shù),或者作為Windows系統(tǒng)函數(shù)的回調(diào)函數(shù)等等。函數(shù)指針還有很多其他的應(yīng)用。函數(shù)指針的實(shí)現(xiàn)很簡(jiǎn)單:它們只是“代碼指針(code pointer)”,它們體現(xiàn)在匯編語(yǔ)言中是用來(lái)保存子程序代碼的首地址。而這種函數(shù)指針的存在只是為了保證使用了正確的調(diào)用規(guī)范。

             

            成員函數(shù)指針

            在C++程序中,很多函數(shù)是成員函數(shù),即這些函數(shù)是某個(gè)類(lèi)中的一部分。你不可以像一個(gè)普通的函數(shù)指針那樣指向一個(gè)成員函數(shù),正確的做法應(yīng)該是,你必須使用一個(gè)成員函數(shù)指針。一個(gè)成員函數(shù)的指針指向類(lèi)中的一個(gè)成員函數(shù),并和以前有相同的參數(shù),聲明如下:

            float (SomeClass::*my_memfunc_ptr)(int, char *);

            對(duì)于使用const關(guān)鍵字修飾的成員函數(shù),聲明如下:

            float (SomeClass::*my_const_memfunc_ptr)(int, char *) const;

            注意使用了特殊的運(yùn)算符(::*),而“SomeClass”是聲明中的一部分。成員函數(shù)指針有一個(gè)可怕的限制:它們只能指向一個(gè)特定的類(lèi)中的成員函數(shù)。對(duì)每一種參數(shù)的組合,需要有不同的成員函數(shù)指針類(lèi)型,而且對(duì)每種使用const修飾的函數(shù)和不同類(lèi)中的函數(shù),也要有不同的函數(shù)指針類(lèi)型。在MSVC中,對(duì)下面這四種調(diào)用方式都有一種不同的調(diào)用類(lèi)型:

            __cdecl, __stdcall, __fastcall, 和 __thiscall。

            (__thiscall是缺省的方式,有趣的是,在任何官方文檔中從沒(méi)有對(duì)__thiscall關(guān)鍵字的詳細(xì)描述,但是它經(jīng)常在錯(cuò)誤信息中出現(xiàn)。如果你顯式地使用它,你會(huì)看到“它被保留作為以后使用(it is reserved for future use)”的錯(cuò)誤提示。)

            如果你使用了成員函數(shù)指針,你最好使用typedef以防止混淆。將函數(shù)指針指向型如float SomeClass::some_member_func(int, char *)的函數(shù),你可以這樣寫(xiě):

            my_memfunc_ptr = &SomeClass::some_member_func;

            很多編譯器(比如MSVC)會(huì)讓你去掉“&”,而其他一些編譯器(比如GNU G++)則需要添加“&”,所以在手寫(xiě)程序的時(shí)候我建議把它添上。若要調(diào)用成員函數(shù)指針,你需要先建立SomeClass的一個(gè)實(shí)例,并使用特殊操作符“->*”,這個(gè)操作符的優(yōu)先級(jí)較低,你需要將其適當(dāng)?shù)胤湃雸A括號(hào)內(nèi)。

            SomeClass *x = new SomeClass;

            (x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter");

            //如果類(lèi)在棧上,你也可以使用“.*”運(yùn)算符。

            SomeClass y;

            (y.*my_memfunc_ptr)(15, "Different parameters this time");

            不要怪我使用如此奇怪的語(yǔ)法——看起來(lái)C++的設(shè)計(jì)者對(duì)標(biāo)點(diǎn)符號(hào)有著由衷的感情!C++相對(duì)于C增加了三種特殊運(yùn)算符來(lái)支持成員指針。“::*”用于指針的聲明,而“->*”和“.*”用來(lái)調(diào)用指針指向的函數(shù)。這樣看起來(lái)對(duì)一個(gè)語(yǔ)言模糊而又很少使用的部分的過(guò)分關(guān)注是多余的。(你當(dāng)然可以重載“->*”這些運(yùn)算符,但這不是本文所要涉及的范圍。)

            一個(gè)成員函數(shù)指針可以被設(shè)置成0,并可以使用“==”和“!=”比較運(yùn)算符,但只能限定在同一個(gè)類(lèi)中的成員函數(shù)的指針之間進(jìn)行這樣的比較。任何成員函數(shù)指針都可以和0做比較以判斷它是否為空。與函數(shù)指針不同,不等運(yùn)算符(<, >, <=, >=)對(duì)成員函數(shù)指針是不可用的。

             

            成員函數(shù)指針的怪異之處

            成員函數(shù)指針有時(shí)表現(xiàn)得很奇怪。

            首先,你不可以用一個(gè)成員函數(shù)指針指向一個(gè)靜態(tài)成員函數(shù),你必須使用普通的函數(shù)指針才行(在這里“成員函數(shù)指針”會(huì)產(chǎn)生誤解,它實(shí)際上應(yīng)該是“非靜態(tài)成員函數(shù)指針”才對(duì))。

            其次,當(dāng)使用類(lèi)的繼承時(shí),會(huì)出現(xiàn)一些比較奇怪的情況。比如,下面的代碼在MSVC下會(huì)編譯成功(注意代碼注釋?zhuān)?/p>

            #include “stdio.h”

            class SomeClass {

            public:

            virtual void some_member_func(int x, char *p) {

            printf("In SomeClass"); };

            };

            class DerivedClass : public SomeClass {

            public:

            // 如果你把下一行的注釋銷(xiāo)掉,帶有 line (*)的那一行會(huì)出現(xiàn)錯(cuò)誤

            // virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); };

            };

            int main() {

            //聲明SomeClass的成員函數(shù)指針

            typedef void (SomeClass::*SomeClassMFP)(int, char *);

            SomeClassMFP my_memfunc_ptr;

            my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*)

            return 0;

            }

            奇怪的是,&DerivedClass::some_member_func是一個(gè)SomeClass類(lèi)的成員函數(shù)指針,而不是DerivedClass類(lèi)的成員函數(shù)指針!(一些編譯器稍微有些不同:比如,對(duì)于Digital Mars C++,在上面的例子中,&DerivedClass::some_member_func會(huì)被認(rèn)為沒(méi)有定義。)但是,如果在DerivedClass類(lèi)中重寫(xiě)(override)了some_member_func函數(shù),代碼就無(wú)法通過(guò)編譯,因?yàn)楝F(xiàn)在的&DerivedClass::some_member_func已成為DerivedClass類(lèi)中的成員函數(shù)指針!

            成員函數(shù)指針之間的類(lèi)型轉(zhuǎn)換是一個(gè)討論起來(lái)非常模糊的話題。在C++的標(biāo)準(zhǔn)化的過(guò)程中,在涉及繼承的類(lèi)的成員函數(shù)指針時(shí),對(duì)于將成員函數(shù)指針轉(zhuǎn)化為基類(lèi)的成員函數(shù)指針還是轉(zhuǎn)化為子類(lèi)成員函數(shù)指針的問(wèn)題是否可以將一個(gè)類(lèi)的成員函數(shù)指針轉(zhuǎn)化為另一個(gè)不相關(guān)的類(lèi)的成員函數(shù)指針的問(wèn)題,人們?cè)羞^(guò)很激烈的爭(zhēng)論。然而不幸的是,在標(biāo)準(zhǔn)委員會(huì)做出決定之前,不同的編譯器生產(chǎn)商已經(jīng)根據(jù)自己對(duì)這些問(wèn)題的不同的回答實(shí)現(xiàn)了自己的編譯器。根據(jù)標(biāo)準(zhǔn)(第

            在一些編譯器中,在基類(lèi)和子類(lèi)的成員函數(shù)指針之間的轉(zhuǎn)換時(shí)常有怪事發(fā)生。當(dāng)涉及到多重繼承時(shí),使用reinterpret_cast將子類(lèi)轉(zhuǎn)換成基類(lèi)時(shí),對(duì)某一特定編譯器來(lái)說(shuō)有可能通過(guò)編譯,而也有可能通不過(guò)編譯,這取決于在子類(lèi)的基類(lèi)列表中的基類(lèi)的順序!下面就是一個(gè)例子:

            class Derived: public Base1, public Base2 // 情況 (a)

            class Derived2: public Base2, public Base1 // 情況 (b)

            typedef void (Derived::* Derived_mfp)();

            typedef void (Derived2::* Derived2_mfp)();

            typedef void (Base1::* Base1mfp) ();

            typedef void (Base2::* Base2mfp) ();

            Derived_mfp x;

            對(duì)于情況(a)static_cast<Base1mfp>(x)是合法的,而static_cast<Base2mfp>(x)則是錯(cuò)誤的。然而情況(b)卻與之相反。你只可以安全地將子類(lèi)的成員函數(shù)指針轉(zhuǎn)化為第一個(gè)基類(lèi)的成員函數(shù)指針!如果你要實(shí)驗(yàn)一下,MSVC會(huì)發(fā)出C4407號(hào)警告,而Digital Mars C++會(huì)出現(xiàn)編譯錯(cuò)誤。如果用reinterpret_cast代替static_cast這兩個(gè)編譯器都會(huì)發(fā)生錯(cuò)誤,但是兩種編譯器對(duì)此有著不同的原因。但是一些編譯器對(duì)此細(xì)節(jié)置之不理,大家可要小心了!

            標(biāo)準(zhǔn)C++中另一條有趣的規(guī)則是:你可以在類(lèi)定義之前聲明它的成員函數(shù)指針。這對(duì)一些編譯器會(huì)有一些無(wú)法預(yù)料的副作用。我待會(huì)討論這個(gè)問(wèn)題,現(xiàn)在你只要知道要盡可能得避免這種情況就是了。

            值得注意的是,就像成員函數(shù)指針,標(biāo)準(zhǔn)C++中同樣提供了成員數(shù)據(jù)指針(member data pointer)。它們具有相同的操作符,而且有一些實(shí)現(xiàn)原則也是相同的。它們用在stl::stable_sort的一些實(shí)現(xiàn)方案中,而對(duì)此很多其他的應(yīng)用我就不再提及了。

             

            成員函數(shù)指針的使用

            現(xiàn)在你可能會(huì)覺(jué)得成員函數(shù)指針是有些奇異。但它可以用來(lái)做什么呢?對(duì)此我在網(wǎng)上做了非常廣泛的調(diào)查。最后我總結(jié)出使用成員函數(shù)指針的兩點(diǎn)原因:

            • 用來(lái)做例子給C++初學(xué)者看,幫助它們學(xué)習(xí)語(yǔ)法;或者
            • 為了實(shí)現(xiàn)“委托(delegate)”!

            成員函數(shù)指針在STL和Boost庫(kù)的單行函數(shù)適配器(one-line function adaptor)中的使用是微不足道的,而且允許你將成員函數(shù)和標(biāo)準(zhǔn)算法混合使用。但是它們最重要的應(yīng)用是在不同類(lèi)型的應(yīng)用程序框架中,比如它們形成了MFC消息系統(tǒng)的核心。

            當(dāng)你使用MFC的消息映射宏(比如ON_COMMAND)時(shí),你會(huì)組裝一個(gè)包含消息ID和成員函數(shù)指針(型如:CCmdTarget::*成員函數(shù)指針)的序列。這是MFC類(lèi)必須繼承CCmdTarget才可以處理消息的原因之一。但是,各種不同的消息處理函數(shù)具有不同的參數(shù)列表(比如OnDraw處理函數(shù)的第一個(gè)參數(shù)的類(lèi)型為CDC *),所以序列中必須包含各種不同類(lèi)型的成員函數(shù)指針。

            MFC是怎樣做到這一點(diǎn)的呢?MFC利用了一個(gè)可怕的編譯器漏洞(hack),它將所有可能出現(xiàn)的成員函數(shù)指針?lè)诺揭粋€(gè)龐大的聯(lián)合(union)中,從而避免了通常需要進(jìn)行的C++類(lèi)型匹配檢查。(看一下afximpl.h和cmdtarg.cpp中名為MessageMapFunctions的union,你就會(huì)發(fā)現(xiàn)這一恐怖的事實(shí)。)

            因?yàn)镸FC有如此重要的一部分代碼,所以事實(shí)是,所有的編譯器都為這個(gè)漏洞開(kāi)了綠燈。(但是,在后面我們會(huì)看到,如果一些類(lèi)用到了多重繼承,這個(gè)漏洞在MSVC中就不會(huì)起作用,這正是在使用MFC時(shí)只能必須使用單一繼承的原因。

            在boost::function中有類(lèi)似的漏洞(但不是太嚴(yán)重)。看起來(lái)如果你想做任何有關(guān)成員函數(shù)指針的比較有趣的事,你就必須做好與這個(gè)語(yǔ)言的漏洞進(jìn)行挑戰(zhàn)的準(zhǔn)備。要是你想否定C++的成員函數(shù)指針設(shè)計(jì)有缺陷的觀點(diǎn),看來(lái)是很難的。

            在寫(xiě)這篇文章中,我有一點(diǎn)需要指明:“允許成員函數(shù)指針之間進(jìn)行轉(zhuǎn)換(cast),而不允許在轉(zhuǎn)換完成后調(diào)用其中的函數(shù)”,把這個(gè)規(guī)則納入C++的標(biāo)準(zhǔn)中是可笑的。

            首先,很多流行的編譯器對(duì)這種轉(zhuǎn)換不支持(所以,轉(zhuǎn)換是標(biāo)準(zhǔn)要求的,但不是可移植的)。

            其次,所有的編譯器,如果轉(zhuǎn)換成功,調(diào)用轉(zhuǎn)換后的成員函數(shù)指針時(shí)仍然可以實(shí)現(xiàn)你預(yù)期的功能:那編譯器就沒(méi)有所謂的“undefined behavior(未定義的行為)”這類(lèi)錯(cuò)誤出現(xiàn)的必要了(調(diào)用(Invocation)是可行的,但這不是標(biāo)準(zhǔn)!)。

            第三,允許轉(zhuǎn)換而不允許調(diào)用是完全沒(méi)有用處的,只有轉(zhuǎn)換和調(diào)用都可行,才能方便而有效地實(shí)現(xiàn)委托,從而使這種語(yǔ)言受益。

            為了讓你確信這一具有爭(zhēng)議的論斷,考慮一下在一個(gè)文件中只有下面的一段代碼,這段代碼是合法的:

            class SomeClass;

            typedef void (SomeClass::* SomeClassFunction)(void);

            void Invoke(SomeClass *pClass, SomeClassFunction funcptr) {(pClass->*funcptr)(); };

            注意到編譯器必須生成匯編代碼來(lái)調(diào)用成員函數(shù)指針,其實(shí)編譯器對(duì)SomeClass類(lèi)一無(wú)所知。顯然,除非鏈接器進(jìn)行了一些極端精細(xì)的優(yōu)化措施,否則代碼會(huì)忽視類(lèi)的實(shí)際定義而能夠正確地運(yùn)行。而這造成的直接后果是,你可以“安全地”調(diào)用從完全不同的其他類(lèi)中轉(zhuǎn)換過(guò)來(lái)的成員函數(shù)指針。

            為解釋我的斷言的另一半——轉(zhuǎn)換并不能按照標(biāo)準(zhǔn)所說(shuō)的方式進(jìn)行,我需要在細(xì)節(jié)上討論編譯器是怎樣實(shí)現(xiàn)成員函數(shù)指針的。我同時(shí)會(huì)解釋為什么使用成員函數(shù)指針的規(guī)則具有如此嚴(yán)格的限制。獲得詳細(xì)論述成員函數(shù)指針的文檔不是太容易,并且大家對(duì)錯(cuò)誤的言論已經(jīng)習(xí)以為常了,所以,我仔細(xì)檢查了一系列編譯器生成的匯編代碼……

             

            成員函數(shù)指針——為什么那么復(fù)雜?

            類(lèi)的成員函數(shù)和標(biāo)準(zhǔn)的C函數(shù)有一些不同。與被顯式聲明的參數(shù)相似,類(lèi)的成員函數(shù)有一個(gè)隱藏的參數(shù)this,它指向一個(gè)類(lèi)的實(shí)例。根據(jù)不同的編譯器,this或者被看作內(nèi)部的一個(gè)正常的參數(shù),或者會(huì)被特別對(duì)待(比如,在VC++中,this一般通過(guò)ECX寄存器來(lái)傳遞,而普通的成員函數(shù)的參數(shù)被直接壓在堆棧中)。this作為參數(shù)和其他普通的參數(shù)有著本質(zhì)的不同,即使一個(gè)成員函數(shù)受一個(gè)普通函數(shù)的支配,在標(biāo)準(zhǔn)C++中也沒(méi)有理由使這個(gè)成員函數(shù)和其他的普通函數(shù)(ordinary function)的行為相同,因?yàn)闆](méi)有thiscall關(guān)鍵字來(lái)保證它使用像普通參數(shù)一樣正常的調(diào)用規(guī)則。成員函數(shù)是一回事,普通函數(shù)是另外一回事(Member functions are from Mars, ordinary functions are from Venus)。

            你可能會(huì)猜測(cè),一個(gè)成員函數(shù)指針和一個(gè)普通函數(shù)指針一樣,只是一個(gè)代碼指針。然而這種猜測(cè)也許是錯(cuò)誤的。在大多數(shù)編譯器中,一個(gè)成員函數(shù)指針要比一個(gè)普通的函數(shù)指針要大許多。更奇怪的是,在Visual C++中,一個(gè)成員函數(shù)指針可以是4、8、12甚至16個(gè)字節(jié)長(zhǎng),這取決于它所相關(guān)的類(lèi)的性質(zhì),同時(shí)也取決于編譯器使用了怎樣的編譯設(shè)置!成員函數(shù)指針比你想象中的要復(fù)雜得多,但也不總是這樣。

            讓我們回到二十世紀(jì)80年代初期,那時(shí),最古老的C++編譯器CFront剛剛開(kāi)發(fā)完成,那時(shí)C++語(yǔ)言只能實(shí)現(xiàn)單一繼承,而且成員函數(shù)指針剛被引入,它們很簡(jiǎn)單:它們就像普通的函數(shù)指針,只是附加了額外的this作為它們的第一個(gè)參數(shù),你可以將一個(gè)成員函數(shù)指針轉(zhuǎn)化成一個(gè)普通的函數(shù)指針,并使你能夠?qū)@個(gè)額外添加的參數(shù)產(chǎn)生足夠的重視。

            這個(gè)田園般的世界隨著CFront 2.0的問(wèn)世被擊得粉碎。它引入了模版和多重繼承,多重繼承所帶來(lái)的破壞造成了成員函數(shù)指針的改變。問(wèn)題在于,隨著多重繼承,調(diào)用之前你不知道使用哪一個(gè)父類(lèi)的this指針,比如,你有4個(gè)類(lèi)定義如下:

            class A {

            public:

            virtual int Afunc() { return 2; };

            };

            class B {

            public:

            int Bfunc() { return 3; };

            };

            // C是個(gè)單一繼承類(lèi),它只繼承于A

            class C: public A {

            public:

            int Cfunc() { return 4; };

            };

            // D 類(lèi)使用了多重繼承

            class D: public A, public B {

            public:

            int Dfunc() { return 5; };

            };

            假如我們建立了C類(lèi)的一個(gè)成員函數(shù)指針。在這個(gè)例子中,Afunc和Cfunc都是C的成員函數(shù),所以我們的成員函數(shù)指針可以指向Afunc或者Cfunc。但是Afunc需要一個(gè)this指針指向C::A(后面我叫它Athis),而Cfunc需要一個(gè)this指針指向C(后面我叫它Cthis)。編譯器的設(shè)計(jì)者們?yōu)榱颂幚磉@種情況使用了一個(gè)把戲(trick):他們保證了A類(lèi)在物理上保存在C類(lèi)的頭部(即C類(lèi)的起始地址也就是一個(gè)A類(lèi)的一個(gè)實(shí)例的起始地址),這意味著Athis == Cthis。我們只需擔(dān)心一個(gè)this指針就夠了,并且對(duì)于目前這種情況,所有的問(wèn)題處理得還可以。

            現(xiàn)在,假如我們建立一個(gè)D類(lèi)的成員函數(shù)指針。在這種情況下,我們的成員函數(shù)指針可以指向Afunc、Bfunc或Dfunc。但是Afunc需要一個(gè)this指針指向D::A,而B(niǎo)func需要一個(gè)this指針指向D::B。這時(shí),這個(gè)把戲就不管用了,我們不可以把A類(lèi)和B類(lèi)都放在D類(lèi)的頭部。所以,D類(lèi)的一個(gè)成員函數(shù)指針不僅要說(shuō)明要指明調(diào)用的是哪一個(gè)函數(shù),還要指明使用哪一個(gè)this指針。編譯器知道A類(lèi)占用的空間有多大,所以它可以對(duì)Athis增加一個(gè)delta = sizeof(A)偏移量就可以將Athis指針轉(zhuǎn)換為Bthis指針。

            如果你使用虛擬繼承(virtual inheritance),比如虛基類(lèi),情況會(huì)變得更糟,你可以不必為搞懂這是為什么太傷腦筋。就舉個(gè)例子來(lái)說(shuō)吧,編譯器使用虛擬函數(shù)表(virtual function table——“vtable”)來(lái)保存每一個(gè)虛函數(shù)、函數(shù)的地址和virtual_delta:將當(dāng)前的this指針轉(zhuǎn)換為實(shí)際函數(shù)需要的this指針時(shí)所要增加的位移量。

            綜上所述,為了支持一般形式的成員函數(shù)指針,你需要至少三條信息:函數(shù)的地址,需要增加到this指針上的delta位移量,和一個(gè)虛擬函數(shù)表中的索引。對(duì)于MSVC來(lái)說(shuō),你需要第四條信息:虛擬函數(shù)表(vtable)的地址。

             

            成員函數(shù)指針的實(shí)現(xiàn)

            那么,編譯器是怎樣實(shí)現(xiàn)成員函數(shù)指針的呢?這里是對(duì)不同的32、64和16位的編譯器,對(duì)各種不同的數(shù)據(jù)類(lèi)型(有int、void*數(shù)據(jù)指針、代碼指針(比如指向靜態(tài)函數(shù)的指針)、在單一(single-)繼承、多重(multiple-)繼承、虛擬(virtual-)繼承和未知類(lèi)型(unknown)的繼承下的類(lèi)的成員函數(shù)指針)使用sizeof運(yùn)算符計(jì)算所獲得的數(shù)據(jù):

            編譯器

            選項(xiàng)

            int

            DataPtr

            CodePtr

            Single

            Multi

            Virtual

            Unknown

            MSVC

             

            4

            4

            4

            4

            8

            12

            16

            MSVC

            /vmg

            4

            4

            4

            16#

            16#

            16#

            16

            MSVC

            /vmg /vmm

            4

            4

            4

            8#

            8#

            --

            8#

            Intel_IA32

             

            4

            4

            4

            4

            8

            12

            12

            Intel_IA32

            /vmg /vmm

            4

            4

            4

            4

            8

            --

            8

            Intel_Itanium

             

            4

            8

            8

            8

            12

            20

            20

            G++

             

            4

            4

            4

            8

            8

            8

            8

            Comeau

             

            4

            4

            4

            8

            8

            8

            8

            DMC

             

            4

            4

            4

            4

            4

            4

            4

            BCC32

             

            4

            4

            4

            12

            12

            12

            12

            BCC32

            /Vmd

            4

            4

            4

            4

            8

            12

            12

            WCL386

             

            4

            4

            4

            12

            12

            12

            12

            CodeWarrior

             

            4

            4

            4

            12

            12

            12

            12

            XLC

             

            4

            8

            8

            20

            20

            20

            20

            DMC

            small

            2

            2

            2

            2

            2

            2

            2

            DMC

            medium

            2

            2

            4

            4

            4

            4

            4

            WCL

            small

            2

            2

            2

            6

            6

            6

            6

            WCL

            compact

            2

            4

            2

            6

            6

            6

            6

            WCL

            medium

            2

            2

            4

            8

            8

            8

            8

            WCL

            large

            2

            4

            4

            8

            8

            8

            8

            注:

            # 表示使用__single/__multi/__virtual_inheritance關(guān)鍵字的時(shí)候代表4、8或12。

            這些編譯器是Microsoft Visual C++ 4.0 to 7.1 (.NET 2003), GNU G++ 3.2 (MingW binaries, http://www.mingw.org/), Borland BCB 5.1 (http://www.borland.com/), Open Watcom (WCL) 1.2 (http://www.openwatcom.org/), Digital Mars (DMC) 8.38n (http://www.digitalmars.com/), Intel C++ 8.0 for Windows IA-32, Intel C++ 8.0 for Itanium, (http://www.intel.com/), IBM XLC for AIX (Power, PowerPC), Metrowerks Code Warrior 9.1 for Windows (http://www.metrowerks.com/), 和 Comeau C++ 4.3 (http://www.comeaucomputing.com/). Comeau的數(shù)據(jù)是在它支持的32位平臺(tái)(x86, Alpha, SPARC等)上得出的。16位的編譯器的數(shù)據(jù)在四種DOS配置(tiny, compact, medium, 和 large)下測(cè)試得出,用來(lái)顯示各種不同代碼和數(shù)據(jù)指針的大小。MSVC在/vmg的選項(xiàng)下進(jìn)行了測(cè)試,用來(lái)顯示“成員指針的全部特性”。(如果你擁有在列表中沒(méi)有出現(xiàn)的編譯器,請(qǐng)告知我。非x86處理機(jī)下的編譯器測(cè)試結(jié)果有獨(dú)特的價(jià)值。)

             

            看著表中的數(shù)據(jù),你是不是覺(jué)得很驚奇?你可以清楚地看到編寫(xiě)一段在一些環(huán)境中可以運(yùn)行而在另一些編譯器中不能運(yùn)行的代碼是很容易的。不同的編譯器之間,它們的內(nèi)部實(shí)現(xiàn)顯然是有很大差別的;事實(shí)上,我認(rèn)為編譯器在實(shí)現(xiàn)語(yǔ)言的其他特性上并沒(méi)有這樣明顯的差別。對(duì)實(shí)現(xiàn)的細(xì)節(jié)進(jìn)行研究你會(huì)發(fā)現(xiàn)一些奇怪的問(wèn)題。

            一般,編譯器采取最差的,而且一直使用最普通的形式。比如對(duì)于下面這個(gè)結(jié)構(gòu):

            // Borland (缺省設(shè)置) 和Watcom C++.

            struct {

            FunctionPointer m_func_address;

            int m_delta;

            int m_vtable_index; //如果不是虛擬繼承,這個(gè)值為0。

            };

            // Metrowerks CodeWarrior使用了稍微有些不同的方式。

            //即使在不允許多重繼承的Embedded C++的模式下,它也使用這樣的結(jié)構(gòu)!

            struct {

            int m_delta;

            int m_vtable_index; // 如果不是虛擬繼承,這個(gè)值為-1。

            FunctionPointer m_func_address;

            };

            // 一個(gè)早期的SunCC版本顯然使用了另一種規(guī)則:

            struct {

            int m_vtable_index; //如果是一個(gè)非虛擬函數(shù)(non-virtual function),這個(gè)值為0。

            FunctionPointer m_func_address; //如果是一個(gè)虛擬函數(shù)(virtual function),這個(gè)值為0。

            int m_delta;

            };

            //下面是微軟的編譯器在未知繼承類(lèi)型的情況下或者使用/vmg選項(xiàng)時(shí)使用的方法:

            struct {

            FunctionPointer m_func_address;

            int m_delta;

            int m_vtordisp;

            int m_vtable_index; // 如果不是虛擬繼承,這個(gè)值為0

            };

            // AIX (PowerPC)上IBM的XLC編譯器:

            struct {

            FunctionPointer m_func_address; // 對(duì)PowerPC來(lái)說(shuō)是64位

            int m_vtable_index;

            int m_delta;

            int m_vtordisp;

            };

            // GNU g++使用了一個(gè)機(jī)靈的方法來(lái)進(jìn)行空間優(yōu)化

            struct {

            union {

            FunctionPointer m_func_address; // 其值總是4的倍數(shù)

            int m_vtable_index_2; // 其值被2除的結(jié)果總是奇數(shù)

            };

            int m_delta;

            };

            對(duì)于幾乎所有的編譯器,delta和vindex用來(lái)調(diào)整傳遞給函數(shù)的this指針,比如Borland的計(jì)算方法是:

            adjustedthis = *(this + vindex -1) + delta // 如果vindex!=0

            adjustedthis = this + delta // 如果vindex=0

            (其中,“*”是提取該地址中的數(shù)值,adjustedthis是調(diào)整后的this指針——譯者注)

            Borland使用了一個(gè)優(yōu)化方法:如果這個(gè)類(lèi)是單一繼承的,編譯器就會(huì)知道delta和vindex的值是0,所以它就可以跳過(guò)上面的計(jì)算方法。

            GNU編譯器使用了一個(gè)奇怪的優(yōu)化方法。可以清楚地看到,對(duì)于多重繼承來(lái)說(shuō),你必須查看vtable(虛擬函數(shù)表)以獲得voffset(虛擬函數(shù)偏移地址)來(lái)計(jì)算this指針。當(dāng)你做這些事情的時(shí)候,你可能也把函數(shù)指針保存在vtable中。通過(guò)這些工作,編譯器將m_func_address和m_vtable_index合二為一(即放在一個(gè)union中),編譯器區(qū)別這兩個(gè)變量的方法是使函數(shù)指針(m_func_address)的值除以2后結(jié)果為偶數(shù),而虛擬函數(shù)表索引(m_vtable_index_2)除以2后結(jié)果為奇數(shù)。它們的計(jì)算方法是:

            adjustedthis = this + delta

            if (funcadr & 1) //如果是奇數(shù)

            call (* ( *delta + (vindex+1)/2) + 4)

            else //如果是偶數(shù)

            call funcadr

            (其中, funcadr是函數(shù)地址除以2得出的結(jié)果。——譯者注)

            Inter的Itanium編譯器(但不是它們的x86編譯器)對(duì)虛擬繼承(virtual inheritance)的情況也使用了unknown_inheritance結(jié)構(gòu),所以,一個(gè)虛擬繼承的指針有20字節(jié)大小,而不是想象中的16字節(jié)。

            // Itanium,unknown 和 virtual inheritance下的情況.

            struct {

            FunctionPointer m_func_address; //對(duì)Itanium來(lái)說(shuō)是64位

            int m_delta;

            int m_vtable_index;

            int m_vtordisp;

            };

            我不能保證Comeau C++使用的是和GNU相同的技術(shù),也不能保證它們是否使用short代替int使這種虛擬函數(shù)指針的結(jié)構(gòu)的大小縮小至8個(gè)字節(jié)。最近發(fā)布的Comeau C++版本為了兼容微軟的編譯器也使用了微軟的編譯器關(guān)鍵字(我想它也只是忽略這些關(guān)鍵字而不對(duì)它們進(jìn)行實(shí)質(zhì)的相關(guān)處理罷了)。

            Digital Mars編譯器(即最初的Zortech C++到后來(lái)的Symantec C++)使用了一種不同的優(yōu)化方法。對(duì)單一繼承類(lèi)來(lái)說(shuō),一個(gè)成員函數(shù)指針僅僅是這個(gè)函數(shù)的地址。但涉及到更復(fù)雜的繼承時(shí),這個(gè)成員函數(shù)指針指向一個(gè)形式轉(zhuǎn)換函數(shù)(thunk function),這個(gè)函數(shù)可以實(shí)現(xiàn)對(duì)this指針的必要調(diào)整并可用來(lái)調(diào)用實(shí)際的成員函數(shù)。每當(dāng)涉及到多重繼承的時(shí)候,每一個(gè)成員函數(shù)的指針都會(huì)有這樣一個(gè)形式轉(zhuǎn)換函數(shù),這對(duì)函數(shù)調(diào)用來(lái)說(shuō)是非常有效的。但是這意味著,當(dāng)使用多重繼承的時(shí)候,子類(lèi)的成員函數(shù)指針向基類(lèi)成員函數(shù)指針的轉(zhuǎn)換就會(huì)不起作用了。可見(jiàn),這種編譯器對(duì)編譯代碼的要求比其他的編譯器要嚴(yán)格得多。

            很多嵌入式系統(tǒng)的編譯器不允許多重繼承。這樣,這些編譯器就避免了可能出現(xiàn)的問(wèn)題:一個(gè)成員函數(shù)指針就是一個(gè)帶有隱藏this指針參數(shù)的普通函數(shù)指針。

             

            微軟"smallest for class"方法的問(wèn)題

            微軟的編譯器使用了和Borland相似的優(yōu)化方法。它們都使單一繼承的情況具有最優(yōu)的效率。但不像Borland,微軟在缺省條件下成員函數(shù)指針省略了值為0 的指針入口(entry),我稱(chēng)這種技術(shù)為“smallest for class”方法:對(duì)單一繼承類(lèi)來(lái)說(shuō),一個(gè)成員函數(shù)指針僅保存了函數(shù)的地址(m_func_address),所以它有4字節(jié)長(zhǎng)。而對(duì)于多重繼承類(lèi)來(lái)說(shuō),由于用到了偏移地址(m_delta),所以它有8字節(jié)長(zhǎng)。對(duì)虛擬繼承,會(huì)用到12個(gè)字節(jié)。這種方法確實(shí)節(jié)省空間,但也有其它的問(wèn)題。

            首先,將一個(gè)成員函數(shù)指針在子類(lèi)和基類(lèi)之間進(jìn)行轉(zhuǎn)化會(huì)改變指針的大小!因此,信息是會(huì)丟失的。其次,當(dāng)一個(gè)成員函數(shù)指針在它的類(lèi)定義之前聲明的時(shí)候,編譯器必須算出要分配給這個(gè)指針多少空間,但是這樣做是不安全的,因?yàn)樵诙x之前編譯器不可能知道這個(gè)類(lèi)的繼承方式。對(duì)Intel C++和早期的微軟編譯器來(lái)說(shuō),編譯器僅僅對(duì)指針的大小進(jìn)行猜測(cè),一旦在源文件中猜測(cè)錯(cuò)誤,你的程序會(huì)在運(yùn)行時(shí)莫名其妙地崩潰。所以,微軟的編譯器中增加了一些保留字:__single_inheritance, __multiple_inheritance,和 __virtual_inheritance,并增設(shè)了一些編譯器開(kāi)關(guān)(compiler switch),如/vmg,讓所有的成員函數(shù)指針有相同的大小,而對(duì)原本個(gè)頭小的成員函數(shù)指針的空余部分用0填充。Borland編譯器也增加了一些編譯器開(kāi)關(guān),但沒(méi)有增加新的關(guān)鍵字。Intel的編譯器可以識(shí)別Microsoft增加的那些關(guān)鍵字,但它在能夠找到類(lèi)的定義的情況下會(huì)對(duì)這些關(guān)鍵字不做處理。

            對(duì)于MSVC來(lái)說(shuō),編譯器需要知道類(lèi)的vtable在哪兒;通常就會(huì)有一個(gè)this指針的偏移量(vtordisp),這個(gè)值對(duì)所有這個(gè)類(lèi)中的成員函數(shù)來(lái)說(shuō)是不變的,但對(duì)每個(gè)類(lèi)來(lái)說(shuō)會(huì)是不同的。對(duì)于MSVC,經(jīng)調(diào)整過(guò)的this指針是在原this指針的基礎(chǔ)上經(jīng)過(guò)下面的計(jì)算得出的:

            if (vindex=0) //如果不是虛擬繼承(_virtual_inheritance)

            adjustedthis = this + delta

            else //如果是

            adjustedthis = this + delta + vtordisp + *(*(this + vtordisp) + vindex)

            在虛擬繼承的情況下,vtordisp的值并不保存在__virtual_inheritance指針中,而是在發(fā)現(xiàn)函數(shù)調(diào)用的代碼時(shí),編譯器才將其相應(yīng)的匯編代碼“嵌”進(jìn)去。但是對(duì)于未知類(lèi)型的繼承,編譯器需要盡可能地通過(guò)讀代碼確定它的繼承類(lèi)型,所以,編譯器將虛擬繼承指針(virtual inheritance pointer)分為兩類(lèi)(__virtual_inheritance和__unknown_inheritance)。

            理論上,所有的編譯器設(shè)計(jì)者應(yīng)該在MFP(成員函數(shù)指針)的實(shí)現(xiàn)上有所變革和突破。但在實(shí)際上,這是行不通的,因?yàn)檫@使現(xiàn)在編寫(xiě)的大量代碼都需要改變。微軟曾發(fā)表了一篇非常古老的文章(http://msdn.microsoft.com/archive/en-us/dnarvc/html/jangrayhood.asp)來(lái)解釋Visual C++運(yùn)作的實(shí)現(xiàn)細(xì)節(jié)。這篇文章是Jan Gray寫(xiě)的,他曾在1990年設(shè)計(jì)了Microsoft C++的對(duì)象模型。盡管這篇文章發(fā)表于1994年,但這篇文章仍然很重要——這意味著C++的對(duì)象模型在長(zhǎng)達(dá)15年的時(shí)間里(1990年到2004年)沒(méi)有絲毫改變。

            現(xiàn)在,我想你對(duì)成員函數(shù)指針的事情已經(jīng)知道得太多了。要點(diǎn)是什么?我已為你建立了一個(gè)規(guī)則。雖然各種編譯器的在這方面的實(shí)現(xiàn)方法有很大的不同,但是也有一些有用的共同點(diǎn):不管對(duì)哪種形式的類(lèi),調(diào)用一個(gè)成員函數(shù)指針生成的匯編語(yǔ)言代碼是完全相同的。有一種特例是使用了“smallest for class”技術(shù)的非標(biāo)準(zhǔn)的編譯器,即使是這種情況,差別也是很微小的。這個(gè)事實(shí)可以讓我們繼續(xù)探索怎樣去建立高性能的委托(delegate)。

             

            委托(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(參見(jiàn)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ú)的類(lèi)使用。為了避免這種局限,你需要間接地使用另一種思路:你可以使用模版為每一個(gè)類(lèi)建立一個(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ì)幾乎所有的類(lèi)都是一樣的,在所有平臺(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)一次。一開(kāi)始,我使用了boost::function,但我發(fā)現(xiàn)程序運(yùn)行時(shí),給委托所分配的內(nèi)存空間占用了整個(gè)程序空間的三分之一還要多!“我要真正的委托!”我在內(nèi)心呼喊著,“真正的委托只需要僅僅兩行匯編指令啊!”

            我并不能總是能夠得到我想要的,但后來(lái)我很幸運(yùn)。我在這兒展示的代碼(代碼下載鏈接見(jià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ù)指針的未公開(kāi)知識(shí)才使它能夠這樣工作。如果你很細(xì)心,而且不在意在少數(shù)情況下的一些編譯器相關(guān)(compiler-specific)的代碼,那么高性能的委托機(jī)制在任何C++編譯器下都是可行的。

            訣竅:將任何類(lèi)型的成員函數(shù)指針轉(zhuǎn)化為一個(gè)標(biāo)準(zhǔn)的形式

            我的代碼的核心是一個(gè)能夠?qū)⑷魏晤?lèi)的指針和任何成員函數(shù)指針?lè)謩e轉(zhuǎn)換為一個(gè)通用類(lèi)的指針和一個(gè)通用成員函數(shù)的指針的類(lèi)。由于C++沒(méi)有“通用成員函數(shù)(generic member function)”的類(lèi)型,所以我把所有類(lèi)型的成員函數(shù)都轉(zhuǎn)化為一個(gè)在代碼中未定義的CGenericClass類(lèi)的成員函數(shù)。

            大多數(shù)編譯器對(duì)所有的成員函數(shù)指針平等地對(duì)待,不管他們屬于哪個(gè)類(lèi)。所以對(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ù)的類(lèi)之間沒(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)型檢查)。使用這種方法看來(lái)是不可避免的??boost::function也用到了這種方法。

            對(duì)于其他的一些編譯器(如Visual C++, Intel C++和Borland C++),我們必須將多重(multiple-)繼承和虛擬(virtual-)繼承類(lèi)的成員函數(shù)指針轉(zhuǎn)化為單一(single-)繼承類(lèi)的函數(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ì)單一繼承類(lèi)的函數(shù)指針,則不需要進(jìn)行調(diào)整;對(duì)多重繼承,則只需要一次加法就可完成調(diào)整;對(duì)虛擬繼承...就有些麻煩了。但是這樣做是管用的,并且在大多數(shù)情況下,所有的工作都在編譯時(shí)完成!

            這是最后一個(gè)訣竅。我們?cè)鯓訁^(qū)分不同的繼承類(lèi)型?并沒(méi)有官方的方法來(lái)讓我們區(qū)分一個(gè)類(lèi)是多重繼承的還是其他類(lèi)型的繼承。但是有一種巧妙的方法,你可以查看我在前面給出了一個(gè)列表(見(jiàn)中篇)——對(duì)MSVC,每種繼承方式產(chǎn)生的成員函數(shù)指針的大小是不同的。所以,我們可以基于成員函數(shù)指針的大小使用模版!比如對(duì)多重繼承類(lèi)型來(lái)說(shuō),這只是個(gè)簡(jiǎn)單的計(jì)算。而在確定unknown_inheritance(16字節(jié))類(lèi)型的時(shí)候,也會(huì)采用類(lèi)似的計(jì)算方法。

            對(duì)于微軟和英特爾的編譯器中采用不標(biāo)準(zhǔn)12字節(jié)的虛擬繼承類(lèi)型的指針的情況,我引發(fā)了一個(gè)編譯時(shí)錯(cuò)誤(compile-time error),因?yàn)樾枰粋€(gè)特定的運(yùn)行環(huán)境(workaround)。如果你在MSVC中使用虛擬繼承,要在聲明類(lèi)之前使用FASTDELEGATEDECLARE宏。而這個(gè)類(lèi)必須使用unknown_inheritance(未知繼承類(lèi)型)指針(這相當(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)不起作用,所以你必須在虛擬繼承類(lèi)中使用宏。我的這個(gè)代碼是因?yàn)榫幾g器的bug才可以正確運(yùn)行,你可以查看代碼來(lái)了解更多細(xì)節(jié)。而在遵從標(biāo)準(zhǔn)的編譯器中不需要注意這么多,況且在任何情況下都不會(huì)妨礙FASTDELEGATEDECLARE宏的使用。

            一旦你將類(lèi)的對(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)的模板類(lèi)就行了。實(shí)現(xiàn)其他類(lèi)型的委托的代碼也大都與此相似,只是對(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,也稱(chēng)作多播委托(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)在堆上分配空間就可以了,這意味著需要在委托類(lèi)中添加一個(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)題。怎樣處理返回值?(是將所有返回值類(lèi)型捆綁在一起,還是忽略一部分?)如果把同一個(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)中的方法——允許非空返回類(lèi)型,允許類(lèi)型的隱式轉(zhuǎn)換,并使用更簡(jiǎn)捷的語(yǔ)法結(jié)構(gòu)。如果我有足夠的興趣我會(huì)把代碼寫(xiě)出來(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的虛基類(lèi)。你可以根據(jù)這個(gè)代碼寫(xiě)出更精彩的代碼,下面的代碼只是說(shuō)明使用FastDelegate的語(yǔ)法:

            using namespace fastdelegate;

            int main(void)

            {

            printf("-- FastDelegate demo -- A no-parameter

            delegate is declared using FastDelegate0 ");

            FastDelegate0 noparameterdelegate(&SimpleVoidFunction);

            noparameterdelegate();

            //調(diào)用委托,這一句調(diào)用SimpleVoidFunction()

            printf(" -- Examples using two-parameter delegates (int, char *) -- ");

            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 ");

            } 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

              對(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)議的一部分寫(xiě)這么一篇長(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)以及更多類(lèi)型的委托。我在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:

              我在寫(xiě)這篇文章時(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寫(xiě)代碼。我非常懷念過(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

            posted on 2013-03-02 18:43 Richard Wei 閱讀(1406) 評(píng)論(1)  編輯 收藏 引用 所屬分類(lèi): C++

            FeedBack:
            # re: 成員函數(shù)指針與高性能的C++委托(轉(zhuǎn)載)
            2013-03-04 16:24 | tb
            拜讀了 。。  回復(fù)  更多評(píng)論
              
            久久精品成人免费观看97| 四虎影视久久久免费观看| 亚洲国产精品高清久久久| 人妻精品久久久久中文字幕69| 久久久免费精品re6| 国产激情久久久久影院小草 | 亚洲国产精品久久电影欧美| 日韩精品久久无码中文字幕| 久久精品一区二区| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 久久综合久久综合亚洲| 国产麻豆精品久久一二三| 久久精品国产99久久香蕉| 囯产精品久久久久久久久蜜桃| 久久99精品久久久久久久久久| 久久久久亚洲爆乳少妇无| 97超级碰碰碰久久久久| 久久久久久久91精品免费观看| 久久亚洲私人国产精品| 深夜久久AAAAA级毛片免费看| 91精品国产9l久久久久| 人妻中文久久久久| 超级碰久久免费公开视频| 无码精品久久久天天影视| 亚洲国产成人乱码精品女人久久久不卡| 久久久久亚洲精品日久生情 | 蜜臀久久99精品久久久久久| 无码人妻久久一区二区三区 | 中文无码久久精品| 青春久久| 久久久久亚洲AV综合波多野结衣| 国产精品18久久久久久vr| 久久精品国产精品亚洲毛片| 国产亚洲精久久久久久无码77777| 久久久无码精品午夜| 欧美久久久久久午夜精品| 精品国产婷婷久久久| 日本免费一区二区久久人人澡| 99久久精品国产麻豆| AV狠狠色丁香婷婷综合久久| 久久精品蜜芽亚洲国产AV|