概述
在前面文章《C++對象模型》可以知道,class 中成員函數的類型有:nonstatic member function、static member function、virtual member function;不同類型的成員函數的調用會有不同的表現,以下對每一種類型的成員函數進行簡單的分析。
nonstatic member functions
為了使 nonstatic member function 能夠與非成員函數具有相同的效率,C++ 編譯器内部将 nonstatic member function 轉換為非成員函數,轉換的步驟如下:
- 改寫函數的 signature(函數原型)以添加一個 this 指針;
- 通過 this 指針存取操作 nonstatic data member;
- 将成員函數經過名稱特殊處理轉換為非成員函數;
例如:
class example{
public:
void func();
};
/* 其中成員函數 func() 會被編譯器内部轉換為 void func(example *this)*/
Static member functions
Static member function 和 nonstatic member function 一樣會被編譯器内部轉換為非成員函數,差別是轉換後不存在 this 指針,是以和全局函數一樣,可以做 callback 函數。Static member function 有以下的特性:
- 不能直接存取其 class 中的 nonstatic member;
- 不能被聲明為 const、volatile 或 virtual;
- 一般不需要經由 class object 調用;
virtual member function
獨立的 class
在獨立的 class 中 virtual member function 的調用有兩種方式:指針調用 和 對象調用。例如:
class A {
virtual void func();
};
A a;
A *pA;
a.func();
pA->func();
用指針調用
pA->func()
在編譯器内部被轉換為
( * pA->vptr[1])(pA)
;
用對象調用
a.func()
在内部被當作和非虛拟成員函數一樣處理,即轉換為
A::func(&a)
;是以 virtual 可以inline,在用對象調用的情況下inline 被展開。
單一繼承的 class
具有 virtual member function 的 class,編譯器對其産生一個 vtable, 把指向虛函數位址的指針放在該表格中;繼承含有 virtual member function 類的派生類具有以下的特性:
- 保留 base class 中每個 virtual function 的指針在 vtable 中的索引值及其順序;
- 若 derived class 改寫了該 virtual function,則在表格中對應的項上用新函數的位址替換原來函數的位址;
- 當 derived class 增加新的虛函數,新的虛函數的位址放在 vtable 中靠後的地方,并不影響表格中已有函數的次序;
多重繼承的 class
像單繼承一樣,用基類的指針或引用隻能通路基類中定義(或繼承)的成員,不能通路派生類中引入的成員。當一個類繼承于多個基類的時候,那些基類之間沒有隐含的關系,不允許使用一個基類的指針通路其他基類的成員。
在多重繼承的 virtual function 機制中,其複雜度圍繞在第二個及後繼的 base class 中,以及在執行期間調整 this 指針;一般規則經由指向第二或後繼 base class 的指針或引用來調用 derived class virtual function。
虛繼承的 class
在虛繼承下,對給定虛基類,無論該類在派生層次中作為虛基類出現多少次,隻繼承一個共享的基類子對象。共享的基類子對象稱為虛基類。
指向成員函數的指針
class A {
void func();
virtual void x();
};
對非虛函數
void func()
,取它的指針得到的是函數的實際位址(即綁定在某個 class object 的位址),如:
void (A::*pfunc) () = &A::func;
;
對虛函數
virtual void x()
,取它的位址得到的是
x
在
class A
的 vtable 中的索引值,如:
viod (A::*pfunc) () = &A::x
;
使用 class object 或指向class object 的指針來調用,即用
a.*pfunc()
或
pa->*pfunc()
調用;
Inline Function
當我們在 class 中定義 inline function 時,編譯器會根據函數的複雜程度決定是否真正定義為 inline 類型,對不能真正定義為 inline function 的請求,編譯器可能把它們處理為 static member function;真正的 inline function 的擴充操作是在調用的那一點上,這會帶來參數的求值操作以及臨時性對象的管理;