1. 程式人生 > >成員函式指標與高效能的C++委託(上,中,下)

成員函式指標與高效能的C++委託(上,中,下)

成員函式指標與高效能的 C++委託(上篇)

Member Function Pointers and the Fastest Possible C++ Delegates

撰文: Don Clugston

翻譯:周翔

引子

標準 C++中沒有真正的面向物件的函式指標。這一點對 C++來說是不幸的,因為面向物件的指標(也叫做“閉包( closure)”或“委託( delegate)”)在一些語言中已經證明了它寶貴的價值。在 Delphi (Object Pascal)中,面向物件的函式指標是 Borland視覺化組建庫( VCL Visual Component Library)的基礎。而在目前,

C#使“委託”的概念日趨流行,這也正顯示出 C#這種語言的成功。在很多應用程式中,“委託”簡化了鬆耦合物件的設計模式 [GoF]。這種特性無疑在標準 C++中也會產生很大的作用。

很遺憾, C++中沒有“委託”,它只提供了成員函式指標( member function pointers 。很多程式設計師從沒有用過函式指標,這是有特定的原因的。因為函式指標自身有很多奇怪的語法規則(比如“ ->*”和“ .*”操作符),而且很難找到它們的準確含義,並且你會找到更好的辦法以避免使用函式指標。更具有諷刺意味的是:事實上,編譯器的編寫者如果實現“委託”的話會比他費勁地實現成員函式指標要容易地多!

在這篇文章中,我要揭開成員函式指標那“神祕的蓋子”。在扼要地重述成員函式指標的語法和特性之後,我會向讀者解釋成員函式指標在一些常用的編譯器中是怎樣實現的,然後我會向大家展示編譯器怎樣有效地實現“委託”。最後我會利用這些精深的知識向你展示在 C++編譯器上實現優化而可靠的“委託”的技術。比如,在 Visual C++(6.0, .NET, and .NET 2003)中對單一目標委託( single-target delegate)的呼叫,編譯器僅僅生成兩行彙編程式碼!

函式指標

下面我們複習一下函式指標。在 C C++語言中,一個命名為 my_func_ptr的函式指標指向一個以一個

int和一個 char*為引數的函式,這個函式返回一個浮點值,宣告如下:

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

// 為了便於理解,我強烈推薦你使用 typedef 關鍵字。

// 如果不這樣的話,當函式指標作為一個函式的引數傳遞的時候,

// 程式會變得晦澀難懂。

// 這樣的話,宣告應如下所示:

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

MyFuncPtrType my_func_ptr;

應注意,對每一個函式的引數組合,函式指標的型別應該是不同的。在 Microsoft Visual C++(以下稱 MSVC)中,對三種不同的呼叫方式有不同的型別: __cdecl, __stdcall, __fastcall。如果你的函式指標指向一個型如 float some_func(int, char *)的函式,這樣做就可以了:

my_func_ptr = some_func;

當你想呼叫它所指向的函式時,你可以這樣寫:

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

你可以將一種型別的函式指標轉換成另一種函式指標型別,但你不可以將一個函式指標指向一個 void *型的資料指標。其他的轉換操作就不用詳敘了。一個函式指標可以被設定為 0來表明它是一個空指標。所有的比較運算子( ==, !=, <, >, <=, >=)都可以使用,可以使用“ ==0”或通過一個顯式的布林轉換來測試指標是否為空( null)。

C語言中,函式指標通常用來像 qsort一樣將函式作為引數,或者作為 Windows系統函式的回撥函式等等。函式指標還有很多其他的應用。函式指標的實現很簡單:它們只是“程式碼指標( code pointer)”,它們體現在組合語言中是用來儲存子程式程式碼的首地址。而這種函式指標的存在只是為了保證使用了正確的呼叫規範。

成員函式指標

C++程式中,很多函式是成員函式,即這些函式是某個類中的一部分。你不可以像一個普通的函式指標那樣指向一個成員函式,正確的做法應該是,你必須使用一個成員函式指標。一個成員函式的指標指向類中的一個成員函式,並和以前有相同的引數,宣告如下:

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

//對於使用 const關鍵字修飾的成員函式,宣告如下:

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

注意使用了特殊的運算子( ::*),而“ SomeClass”是宣告中的一部分。成員函式指標有一個可怕的限制:它們只能指向一個特定的類中的成員函式。對每一種引數的組合,需要有不同的成員函式指標型別,而且對每種使用 const修飾的函式和不同類中的函式,也要有不同的函式指標型別。在 MSVC中,對下面這四種呼叫方式都有一種不同的呼叫型別: __cdecl, __stdcall, __fastcall, __thiscall。( __thiscall是預設的方式,有趣的是,在任何官方文件中從沒有對 __thiscall關鍵字的詳細描述,但是它經常在錯誤資訊中出現。如果你顯式地使用它,你會看到“它被保留作為以後使用( it is reserved for future use)”的錯誤提示。)如果你使用了成員函式指標,你最好使用 typedef以防止混淆。

將函式指標指向型如 float SomeClass::some_member_func(int, char *)的函式,你可以這樣寫:

my_memfunc_ptr = &SomeClass::some_member_func;

很多編譯器(比如 MSVC)會讓你去掉“ &”,而其他一些編譯器(比如 GNU G++)則需要新增“ &”,所以在手寫程式的時候我建議把它添上。若要呼叫成員函式指標,你需要先建立 SomeClass的一個例項,並使用特殊操作符“ ->*”,這個操作符的優先順序較低,你需要將其適當地放入圓括號內。

SomeClass *x = new SomeClass;

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

//如果類在棧上,你也可以使用“ .*”運算子。

SomeClass y;

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

不要怪我使用如此奇怪的語法——看起來 C++的設計者對標點符號有著由衷的感情! C++相對於 C增加了三種特殊運算子來支援成員指標。“ ::*”用於指標的宣告,而“ ->*”和“ .*”用來呼叫指標指向的函式。這樣看起來對一個語言模糊而又很少使用的部分的過分關注是多餘的。(你當然可以過載“ ->*”這些運算子,但這不是本文所要涉及的範圍。)

一個成員函式指標可以被設定成 0,並可以使用“ ==”和“ !=”比較運算子,但只能限定在同一個類中的成員函式的指標之間進行這樣的比較。任何成員函式指標都可以和 0做比較以判斷它是否為空。與函式指標不同,不等運算子( <, >, <=, >=)對成員函式指標是不可用的。

成員函式指標的怪異之處

成 員函式指標有時表現得很奇怪。首先,你不可以用一個成員函式指標指向一個靜態成員函式,你必須使用普通的函式指標才行(在這裡“成員函式指標”會產生誤 解,它實際上應該是“非靜態成員函式指標”才對)。其次,當使用類的繼承時,會出現一些比較奇怪的情況。比如,下面的程式碼在 MSVC下會編譯成功(注意程式碼註釋):

#include “stdio.h”

class SomeClass {

public:

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

printf("In SomeClass"); };

};

class DerivedClass : public SomeClass {

public:

// 如果你把下一行的註釋銷掉,帶有 line (*) 的那一行會出現錯誤

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

};

int main() {

// 宣告 SomeClass 的成員函式指標

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

SomeClassMFP my_memfunc_ptr;

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

return 0;

}

奇怪的是, &DerivedClass::some_member_func是一個 SomeClass類的成員函式指標,而不是 DerivedClass類的成員函式指標!(一些編譯器稍微有些不同:比如,對於 Digital Mars C++,在上面的例子中, &DerivedClass::some_member_func會被認為沒有定義。)但是,如果在 DerivedClass類中重寫( override)了 some_member_func函式,程式碼就無法通過編譯,因為現在的 &DerivedClass::some_member_func已成為 DerivedClass類中的成員函式指標!

成員函式指標之間的型別轉換是一個討論起來非常模糊的話題。在 C++的標準化的過程中,在涉及繼承的類的成員函式指標時,對於將成員函式指標轉化為基類的成員函式指標還是轉化為子類成員函式指標的問題是否可以將一個類的成員函式指標轉化為另一個不相關的類的成員函式指標的問題 ,人們曾有過很激烈的爭論。然而不幸的是,在標準委員會做出決定之前,不同的編譯器生產商已經根據自己對這些問題的不同的回答實現了自己的編譯器。根據標準(第 5.2.10/9節),你可以使用 reinterpret_cast 在 一個成員函式指標中儲存一個與本來的類不相關的類的成員函式。有關成員函式指標轉換的問題的最終結果也沒有確定下來。你現在所能做的還是像以前那樣——將 成員函式指標轉化為本類的成員函式的指標。在文章的後面我會繼續討論這個問題,因為這正是各個編譯器對這樣一個標準沒有達成共識的一個話題。

在一些編譯器中,在基類和子類的成員函式指標之間的轉換時常有怪事發生。當涉及到多重繼承時,使用 reinterpret_cast 將子類轉換成基類時,對某一特定編譯器來說有可能通過編譯,而也有可能通不過編譯,這取決於在子類的基類列表中的基類的順序! 下面就是一個例子:

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;

對於情況 (a) static_cast<Base1mfp>(x) 是合法的,而 static_cast<Base2mfp>(x) 則是錯誤的。然而情況 (b) 卻與之相反。你只可以安全地將子類的成員函式指標轉化為第一個基類的成員函式指標!如果你要實驗一下, MSVC會發出 C4407號警告,而 Digital Mars C++會出現編譯錯誤。如果用 reinterpret_cast 代替 static_cast 這兩個編譯器都會發生錯誤,但是兩種編譯器對此有著不同的原因。但是一些編譯器對此細節置之不理,大家可要小心了!

標準 C++中另一條有趣的規則是:你可以在類定義之前宣告它的成員函式指標。 這對一些編譯器會有一些無法預料的副作用。我待會討論這個問題,現在你只要知道要儘可能得避免這種情況就是了。

需要值得注意的是,就像成員函式指標,標準 C++中同樣提供了成員資料指標( member data pointer)。它們具有相同的操作符,而且有一些實現原則也是相同的。它們用在 stl::stable_sort的一些實現方案中,而對此很多其他的應用我就不再提及了。

成員函式指標的使用

現在你可能會覺得成員函式指標是有些奇異。但它可以用來做什麼呢?對此我在網上做了非常廣泛的調查。最後我總結出使用成員函式指標的兩點原因:

用來做例子給 C++初學者看,幫助它們學習語法;或者

為了實現“委託( delegate)”!

成員函式指標在 STL Boost庫的單行函式介面卡( one-line function adaptor)中的使用是微不足道的,而且允許你將成員函式和標準演算法混合使用。但是它們最重要的應用是在不同型別的應用程式框架中,比如它們形成了 MFC訊息系統的核心。

當你使用 MFC的訊息對映巨集(比如 ON_COMMAND)時,你會組裝一個包含訊息 ID和成員函式指標(型如: CCmdTarget::*成員函式指標)的序列。這是 MFC類必須繼承 CCmdTarget才可以處理訊息的原因之一。但是,各種不同的訊息處理函式具有不同的引數列表(比如 OnDraw處理函式的第一個引數的型別為 CDC *),所以序列中必須包含各種不同型別的成員函式指標。 MFC是怎樣做到這一點的呢? MFC利用了一個可怕的編譯器漏洞( hack),它將所有可能出現的成員函式指標放到一個龐大的聯合( union)中,從而避免了通常需要進行的 C++型別匹配檢查。 (看一下 afximpl.h cmdtarg.cpp中名為 MessageMapFunctions union,你就會發現這一恐怖的事實。)因為 MFC有如此重要的一部分程式碼,所以事實是,所有的編譯器都為這個漏洞開了綠燈。(但是,在後面我們會看到,如果一些類用到了多重繼承,這個漏洞在 MSVC中就不會起作用,這正是在使用 MFC時只能必須使用單一繼承的原因。

boost::function中有類似的漏洞(但不是太嚴重)。看起來如果你想做任何有關成員函式指標的比較有趣的事,你就必須做好與這個語言的漏洞進行挑戰的準備。要是你想否定 C++的成員函式指標設計有缺陷的觀點,看來是很難的。

在寫這篇文章中,我有一點需要指明:“允許成員函式指標之間進行轉換( cast),而不允許在轉換完成後呼叫其中的函式” ,把這個規則納入 C++的標準中是可笑的。首先,很多 流行的編譯器對這種轉換不支援(所以,轉換是標準要求的,但不是可移植的)。其次,所有 的編譯器,如果轉換成功,呼叫轉換後的成員函式指標時仍然可以實現你預期的功能:那編譯器就沒有所謂的“ undefined behavior(未定義的行為)”這類錯誤出現的必要了(呼叫( Invocation)是可行的,但這不是標準!)。第三,允許轉換而不允許呼叫是完全沒有用處的,只有轉換和呼叫都可行,才能方便而有效地實現委託,從而使這種語言受益。

為了讓你確信這一具有爭議的論斷,考慮一下在一個檔案中只有下面的一段程式碼,這段程式碼是合法的:

class SomeClass;

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

void Invoke(SomeClass *pClass, SomeClassFunction funcptr) {

(pClass->*funcptr)(); };

注意到編譯器必須生成彙編程式碼來呼叫成員函式指標,其實編譯器對 SomeClass類一無所知。顯然,除非連結器進行了一些極端精細的優化措施,否則程式碼會忽視類的實際定義而能夠正確地執行。而這造成的直接後果是,你可以“安全地”呼叫從完全不同的其他類中轉換過來的成員函式指標。

為 解釋我的斷言的另一半——轉換並不能按照標準所說的方式進行,我需要在細節上討論編譯器是怎樣實現成員函式指標的。我同時會解釋為什麼使用成員函式指標的 規則具有如此嚴格的限制。獲得詳細論述成員函式指標的文件不是太容易,並且大家對錯誤的言論已經習以為常了,所以,我仔細檢查了一系列編譯器生成的彙編代 碼……

中篇

成員函式指標——為什麼那麼複雜?

類的成員函式和標準的 C函式有一些不同。與被顯式宣告的引數相似,類的成員函式有一個隱藏的引數 this ,它指向一個類的例項。根據不同的編譯器, this 或者被看作內部的一個正常的引數,或者會被特別對待(比如,在 VC++中, this一般通過 ECX暫存器來傳遞,而普通的成員函式的引數被直接壓在堆疊中)。t his 作為引數和其他普通的引數有著本質的不同,即使一個成員函式受一個普通函式的支配,在標準 C++中也沒有理由使這個成員函式和其他的普通函式( ordinary function)的行為相同,因為沒有 thiscall 關鍵字來保證它使用像普通引數一樣正常的呼叫規則。成員函式是一回事,普通函式是另外一回事( Member functions are from Mars, ordinary functions are from Venus)。

你可能會猜測,一個成員函式指標和一個普通函式指標一樣,只是一個程式碼指標。然而這種猜測也許是錯誤的。在大多數編譯器中,一個成員函式指標要比一個普通的函式指標要大許多。更奇怪的是,在 Visual C++中,一個成員函式指標可以是 4 8 12甚至 16個位元組長,這取決於它所相關的類的性質,同時也取決於編譯器使用了怎樣的編譯設定!成員函式指標比你想象中的要複雜得多,但也不總是這樣。

讓我們回到二十世紀 80年代初期,那時,最古老的 C++編譯器 CFront剛剛開發完成,那時 C++語言只能實現單一繼承,而且成員函式指標剛被引入,它們很簡單:它們就像普通的函式指標,只是附加了額外的 this 作為它們的第一個引數,你可以將一個成員函式指標轉化成一個普通的函式指標,並使你能夠對這個額外新增的引數產生足夠的重視。

這個田園般的世界隨著 CFront 2.0的問世被擊得粉碎。它引入了模版和多重繼承,多重繼承所帶來的破壞造成了成員函式指標的改變。問題在於,隨著多重繼承,呼叫之前你不知道使用哪一個父類的 this 指標,比如,你有 4個類定義如下:

class A {

public:

virtual int Afunc() { return 2; };

};

class B {

public:

int Bfunc() { return 3; };

};

// C是個單一繼承類,它只繼承於 A

class C: public A {

public:

int Cfunc() { return 4; };

};

// D 類使用了多重繼承

class D: public A, public B {

public:

int Dfunc() { return 5; };

};

假如我們建立了 C類的一個成員函式指標。在這個例子中, Afunc Cfunc都是 C的成員函式,所以我們的成員函式指標可以指向 Afunc或者 Cfunc。但是 Afunc需要一個 this指標指向 C::A(後面我叫它 Athis ),而 Cfunc需要一個 this指標指向 C(後面我叫它 Cthis )。編譯器的設計者們為了處理這種情況使用了一個把戲( trick):他們保證了 A類在物理上儲存在 C類的頭部(即 C類的起始地址也就是一個 A類的一個例項的起始地址),這意味著 Athis == Cthis 。我們只需擔心一個 this指標就夠了,並且對於目前這種情況,所有的問題處理得還可以。

現在,假如我們建立一個 D類的成員函式指標。在這種情況下,我們的成員函式指標可以指向 Afunc Bfunc Dfunc。但是 Afunc需要一個 this指標指向 D::A,而 Bfunc需要一個 this指標指向 D::B。這時,這個把戲就不管用了,我們不可以把 A類和 B類都放在 D類的頭部。所以, D類的一個成員函式指標不僅要說明要指明呼叫的是哪一個函式,還要指明使用哪一個 this指標。編譯器知道 A類佔用的空間有多大,所以它可以對 Athis增加一個 delta = sizeof(A)偏移量就可以將 Athis指標轉換為 Bthis指標。

如果你使用虛擬繼承( virtual inheritance),比如虛基類,情況會變得更糟,你可以不必為搞懂這是為什麼太傷腦筋。就舉個例子來說吧,編譯器使用虛擬函式表 virtual function table——“ vtable”)來儲存每一個虛擬函式、函式的地址和 virtual_delta 將當前的 this指標轉換為實際函式需要的 this指標時所要增加的位移量。

綜上所述,為了支援一般形式的成員函式指標,你需要至少三條資訊:函式的地址,需要增加到 this指標上的 delta位移量,和一個虛擬函式表中的索引。對於 MSVC來說,你需要第四條資訊:虛擬函式表( vtable)的地址。

成員函式指標的實現

那麼,編譯器是怎樣實現成員函式指標的呢?這裡是對不同的 32 64 16位的編譯器,對各種不同的資料型別(有 int void*資料指標、程式碼指標(比如指向靜態函式的指標)、在單一( single-)繼承、多重( multiple-)繼承、虛擬( virtual-)繼承和未知型別( unknown)的繼承下的類的成員函式指標)使用 sizeof運算子計算所獲得的資料:

編譯器

選項

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關鍵字的時候代表 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的資料是在它支援的 32位平臺( x86, Alpha, SPARC等)上得出的。 16位的編譯器的資料在四種 DOS配置( tiny, compact, medium, large)下測試得出,用來顯示各種不同程式碼和資料指標的大小。 MSVC /vmg的選項下進行了測試,用來顯示“成員指標的全部特性”。(如果你擁有在列表中沒有出現的編譯器,請告知我。非 x86處理機下的編譯器測試結果有獨特的價值。)

看 著表中的資料,你是不是覺得很驚奇?你可以清楚地看到編寫一段在一些環境中可以執行而在另一些編譯器中不能執行的程式碼是很容易的。不同的編譯器之間,它們 的內部實現顯然是有很大差別的;事實上,我認為編譯器在實現語言的其他特性上並沒有這樣明顯的差別。對實現的細節進行研究你會發現一些奇怪的問題。

一般,編譯器採取最差的,而且一直使用最普通的形式。比如對於下面這個結構:

// Borland (預設設定 ) Watcom C++.

struct {

FunctionPointer m_func_address;

int m_delta;

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

} ;

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

//即使在不允許多重繼承的 Embedded C++的模式下,它也使用這樣的結構!

struct {

int m_delta;

int m_vtable_index; // 如果不是虛擬繼承,這個值為 -1

FunctionPointer m_func_address;

};

// 一個早期的 SunCC版本顯然使用了另一種規則:

struct {

int m_vtable_index; //如果是一個非虛擬函式( non-virtual function),這個值為 0

FunctionPointer m_func_address; //如果是一個虛擬函式( virtual function),這個值為 0

int m_delta;

};

//下面是微軟的編譯器在未知繼承型別的情況下或者使用 /vmg選項時使用的方法:

struct {

FunctionPointer m_func_address;

int m_delta;

int m_vtordisp;

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

} ;

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

struct {

FunctionPointer m_func_address; // PowerPC來說是 64

int m_vtable_index;

int m_delta;

int m_vtordisp;

};

// GNU g++使用了一個機靈的方法來進行空間優化

struct {

union {

FunctionPointer m_func_address; // 其值總是 4的倍數

int m_vtable_index_2; // 其值被 2除的結果總是奇數

};

int m_delta;

};

對於幾乎所有的編譯器, delta vindex用來調整傳遞給函式的 this指標,比如 Borland的計算方法是:

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

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

(其中,“ *”是提取該地址中的數值, adjustedthis是調整後的 this指標——譯者注)

Borland使用了一個優化方法:如果這個類是單一繼承的,編譯器就會知道delta和vindex的值是0,所以它就可以跳過上面的計算方法。

GNU編譯器使用了一個奇怪的優化方法。可以清楚地看到,對於多重繼承來說,你必須檢視 vtable(虛擬函式表)以獲得 voffset(虛擬函式偏移地址)來計算 this指標。當你做這些事情的時候,你可能也把函式指標儲存在 vtable中。通過這些工作,編譯器將 m_func_address m_vtable_index合二為一(即放在一個 union中),編譯器區別這兩個變數的方法是使函式指標( m_func_address)的值除以 2後結果為偶數,而虛擬函式表索引 (m_vtable_index_2)除以 2後結果為奇數。它們的計算方法是:

adjustedthis = this + delta

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

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

相關推薦

成員函式指標高效能C委託中篇

成員函式指標——為什麼那麼複雜?  類的成員函式和標準的C函式有一些不同。與被顯式宣告的引數相似,類的成員函式有一個隱藏的引數this,它指向一個類的例項。根據不同的編譯器,this或者被看作內部的一個正常的引數,或者會被特別對待(比如,在VC++中,this一般通過ECX暫

成員函式指標高效能C++委託

成員函式指標與高效能的 C++委託(上篇) Member Function Pointers and the Fastest Possible C++ Delegates 撰文: Don Clugston 翻譯:周翔 引子 標準 C++中沒有真正的面

成員函式指標高效能C++委託下篇

Member Function Pointers and the Fastest Possible C++ Delegates 撰文: 翻譯:周翔 (接中篇) 委託(delegate) 和成員函式指標不同,你不難發現委託的用處。最重要的,使用委託可以很容易地實現一個 現

好文轉載:成員函式指標高效能C++委託

委託(delegate)和成員函式指標不同,你不難發現委託的用處。最重要的,使用委託可以很容易地實現一個Subject/Observer設計模式的改進版[GoF, p. 293]。Observer(觀察者)模式顯然在GUI中有很多的應用,但我發現它對應用程式核心的設計也有很大的作用。委託也可用來實現策略(St

成員函式指標高效能C++委託(中篇)

成員函式指標與高效能的C++委託(中篇) Member Function Pointers and the Fastest Possible C++ Delegates 撰文:Don Clugston 翻譯:周翔 (接上篇) 成員函式指標——為什麼那麼複雜? 類

成員函式指標高效能C++委託

Member Function Pointers and the Fastest Possible C++ Delegates 撰文:Don Clugston 翻譯:周翔 引子 標準C++中沒有真正的面向物件的函式指標。這一點對C++來說是不幸的,因為面向物件的指標(也

指標陣列的關係一維、二維

一、指標和一維陣列間的關係 前提條件: int a[10];//元素個數隨意自己定 int *p = a;//定義指標時直接初始化,也可以分為兩步:int *p; p=a; 第i個元素的地址: &am

UILable的字型位置設定、下等

1、看效果 2、介紹用途       例如:下面的設計要求8%的字和返利的框對齊。如圖:        碰到這種情況。單純的uilabel 是無能為力的。有的朋友還不信。可以調整位子來對其啊,但是,

C/C++ 指標小結——指標其它資料型別陣列、字串、函式、結構體的關係

一、指標與陣列和字串 1、指標與陣列 當宣告數時,編譯器在連續的記憶體空間分配基本地址和足夠的儲存空間,以容納陣列的所有元素。基本地址是陣列第一個元素(索引為0)的儲存位置。編譯器還把陣列名定義為指向第一個元素的常量指標。 元素的地址是通過索引和資料型別的比例因子來計算的;例如: x[3

函式指標指標函式C++工廠設計最喜歡用這個

在看開源專案的時候,發現C++搞工廠設計都喜歡用這個。下面來給出這方面的例子(大學裡面沒學過)函式指標:型別一:程式碼如下:#include <iostream> using namespace std; int max(int x, int y){ retu

成員函式指標結構+普通函式指標之間的轉換

通過記憶體拷貝(memcpy等)可以實現任意指標 間的強制轉換,但不能保證可以正常使用。 通過網上查詢發現: 函式成員指標其實與普通成員指標不同,它除了包含函式本身地址以外還包含其他資訊(例如是否為虛擬函式等),所以不能簡單的理解成員函式指標就是普通指標那樣一般佔4位元組

C/C++開發】函式指標回撥函式

C++很多類庫都喜歡用回撥函式,MFC中的定時器,訊息機制,hook機制等待,包括現在在研究的cocos2d-x中也有很多的回撥函式。 1.回撥函式 什麼是回撥函式呢?回撥函式其實就是一個通過函式指標呼叫的函式!假如你把A函式的指標當作引數傳給B函式,然後在B函式中通過A函式傳進來的這個指標

C藝術篇 3-1 指標一維陣列1

我們先來看指標與一維陣列的關係,例題如下: 從輸出結果得知,arr是陣列名,它是指標常量,而ptr是指標變數。 arr表示此陣列第一個元素的地址,即arr等同於&arr[0]。 arr可以使用指標變數的*表示符號,如*arr等同於arr[0],*(arr+1)等同於arr[1],依次

成員函式過載函式指標

在有成員函式過載的情況下該如何使用函式指標呢 class l { public: void func(); void func(int, int); }; void l::func() { cout << "func()" << endl; }

C++函式指標 C++11 function 函式物件對比

轉自:https://blog.csdn.net/skillart/article/details/52336303 1.函式指標 函式指標:是指向函式的指標變數,在C編譯時,每一個函式都有一個入口地址,那麼這個指向這個函式的函式指標便指向這個地址。函式指標主要由以下兩方面的用途:呼叫函式和

C/C++函式指標指標函式

前面說的話 面試的時候,經常有面試官問這個問題,在Linux核心裡面也是經常被使用的,在看很多大神的程式碼裡面,我們也經常遇到函式指標與指標函式,一樣,如果你自己沒問題了,就不用往下看了。   定義 我們看個程式碼 int *func(int a,int b)

為什麼 C++ 成員函式指標是 16 位元組?

當我們討論指標時,通常假設它是一種可以用 void * 指標來表示的東西,在 x86_64 平臺下是 8 個位元組大小。例如,下面是來自 維基百科中關於 x86_64 的文章 的摘錄: Pushes and pops on the stack are always in 8-byte strides

C++類 給結構體成員 函式指標 賦值

myStruct標頭檔案 myStruct.h class CMyClass; struct {  int nFlag;  void (CMyClass::*myinit)(int n);  void (CMyClass::*myopen)(int n,void* arg)

c++函式指標和類成員函式指標

// // main.cpp // Demo // // Created by 杜國超 on 16/12/28. // Copyright © 2016年 杜國超. All rights reserved. // #include using namespace std; class MYCla

C++的靜態成員函式指標

先簡單的說說非靜態的成員函式。 非靜態成員函式指標的型別:     類的非靜態成員是和類的物件相關的。也就是說,要通過類的物件來訪問變數。 成員函式的型別定義為:     typedef void (A::*pfunc)();  A是一個類,有一個成員函式void test