名稱的特殊處理(Name Mangling)
如member:
class bar{public: int ival; …}
class foo :public class bar {public: int ival; …}
經處理後:
class foo{
public:
int ival_3bar;
int ival_4foo;
}
如function:
class point {
public:
void x (float newX);
float x();
…
}
經處理後:
class Point {
public:
void x_5PointFf (float newX);
float x_5PointFv ();
…
}
把參數和函數名稱編碼在一起,編譯器於是在不同的被編譯模組之間達成了一種有限形式的類型檢驗(“傳回型別”的聲明錯誤沒辦法檢查出來)。
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Nonstatic Member Functions (非靜態成員函數)
需經過內部轉化,步驟:
1. 改寫函數原型以安插一個額外的參數到member function 中,用以提供一個存取管道,使class object 得以調用該函數。該額外參數被稱為this 指標:如
Point3d Point3d::magnitude(Point3d *const this)
如果member function 是const , 則變成:
Point3d Point3d::magnitude(const Point3d *const this)
2.將每一個“對nonstatic data member 的存取操作”改為經由this 指標來存取。
3.將member function 重新寫成一個外部函數。對函數名稱進行“mangling”處理,使它在程式中成為獨一無二的語彙。
而函數的叫用作業也都必須轉換。
如:obj.magnitude() 變成了:magnitude_7Point3dFv(&obj);
Static Member Function (靜態成員函數)
如:obj.normalize();ptr->normalize();將都被轉換為一般的nonmember 函數調用,
象這樣:normalize()_7Point3dSFv();
Static member functions 的主要特性就是它沒有this指標,次要特性統統根源於其主要特性:
1. 它不能直接存取其class中的nonstatic members。
2. 它不能夠被聲明為const,volatile 或 virtual。
3. 它不需要經由class object 才被調用——雖然大部分時候它是這樣被調用 的。
如果class object 是因為某個運算式而獲得的,這個運算式仍然需要被評估求值;
如:if ( foo ().objcet_count () > 1) …
被轉換為:(void) foo (); if ( Point3d::object_count() > 1) …
如果取一個static member function 的地址,獲得的將是其在記憶體中的位置,也就是其地址。由於static member function 沒有 this 指標,所以其地址的類型並不是一個“指向class member function 的指標”,而是一個“nonmember 函數指標”,也就是說:
&Point3d::object_count();會得到一個數值,類型是:unsigned int (*) ();
而不是:unsigned int (Point3d::*) ();
Virtual Member Function (虛擬成員函數)
如:ptr->normalize();將被內部轉化為:(*ptr->vptr[1])(this);
明確的叫用作業(explicitly invocation)會壓制虛擬機器制,其決議方式和nonstatic member function一樣。如:register float mag = Point3d::magnitude();//magnitude是virtual function
在C++中,多態(polymorphism)表示“以一個public base class 的指標(或reference),定址出一個derived class object”的意思。
識別一個class 是否支援多態,唯一適當的方法就是看看它是否有任何virtual function。
有這樣的調用:ptr->z();
為了在執行期調用正確需要知道兩點:
1. ptr所指對象的真實類型。這可使我們選擇正確的z()實體;
2. z() 實體位置,以便我能夠調用它。
實現上,可以在每一個多態的class object 身上增加兩個members:
1. 一個字串或數字,表示class 的類型;
2. 一個指標,指向某表格,表格中帶有程式的virtual functions 的執行期地址。
為了在執行期找到virtual function 的地址,需要兩個步驟:
1. 為了找到表格,每一個class object 被安插上一個由編譯器內部產生的指標,指向該表格。
2. 為了找到函數地址,每一個virtual function 被指派一個表格索引值。
單重繼承下的virtual functions:
一個class 只會有一個virtual table。每一個table 內含其對應的class object 中所有active virtual functions 函數實體的地址。這些active virtual functions 包括:
1.這個class 所定義的函數實體。它會改寫(overriding)一個可能存在的base class virtual function 函數實體。
2.繼承自base class 的函數實體。這是在derived class 決定不改寫virtual function 時才會出現的情況。
3.一個pure_virtual_called() 函數實體,它既可以扮演 pure virtual function 的空間保衛者角色,也可以當做執行期異常處理函數(有時會用到)。
調用pure virtual function ,通常會結束掉程式。
多重繼承下的virtual functions:
在多重繼承中支援virtual functions,其複雜度圍繞在第二個及後繼的base classes 身上,以及“必須在執行期調整this 指標”這一點。
如:Base2 *Pbase2 = new Derived;新的Derived 對象的地址必須調整,以指向其Base2 subobject.編譯時間期會產生以下的碼:
//轉移以支援第二個base class
Derived *temp = new Derived;
Base2 *Pbase2 = temp ? temp + sizeof (Base1 ) : 0;
當程式員要刪除Pbase2所指的對象時:指標必須被再一次調整,以求再一次指向Derived 對象的起始處。
在多重繼承之下,一個derived class 內含n-1 個額外的virtual tables,n 表示其上一層 base classes 的數目。
1.一個主要實體,與Base1共用(Vtbl_Derived).
2.一個次要實體,與Base2 有關(Vtbl_Base2_Derived).
允許一個virtual function 的傳回值類型有所變化,可能是base type,也可能是 publicly derived type。
虛擬繼承下的virtual function:
有個建議是,不要在一個virtual base class 中聲明nonstatic data members。
指向member function 的指標(Pointer-to-Member Functions)
取一個nonstatic data member 的地址,得到的結果是該member 在class 布局中的bytes 位置(再加1)。它是一個不完整的值,它需要被綁定於某個class object 的地址上,才能夠被存取。
取一個nonstatic member function 的地址,如果該函數是nonvirtual ,則得到的結果是它在記憶體中真正的地址。這個值也是不完全的,它也需要被綁定於某個class object 的地址上,才能夠通過它調用該函數。所有的nonstatic member functions 都需要對象的地址(以參數this 指出)。
一個指向 member function 的指標,如:
double (Point::*pmf) ()=&Point::x;
x 可以是virtual 或者是nonvirtual 的。但是被解釋的結果不一樣:
((( int ) pmf ) & ~127 ) ? ( *pmf ) ( ptr ) : ( *ptr->vptr[ ( int )pmf ] ( ptr ) )
Inline Functions(內嵌函式)
處理inline 函數,有兩個階段:
1. 分析函數定義,以決定函數的“intrinsic inline ability”(本質的inline 能力)。“intrinsic”(本質的,固有的)一詞在這裡意指“與編譯器有關”。如果函數因其複雜度,或因其建構問題,被判斷不可成為inline,它會被轉為一個static 函數。
2. 真正的inline 函數擴充操作是在調用的那一點上,這會帶來參數的求值操作(evaluation)以及臨時性對象的管理。
形式參數:
一般而言,面對“會帶來副作用的實際參數”,通常都需要引入臨時性對象。
即如果實際參數是一個常量運算式,可以在替換之前先完成求值操作;後繼inline 替換,就可以把常量直接“綁”上去。如果既不是個常量運算式,也不是個帶有副作用的運算式,那麼就直接代換之。
局部變數:
如果inline 函數以單一運算式擴充多次,那麼每次擴充都需要自己的一組局部變數。如果inline 函數以分離的多個式子被擴充多次,那麼只需一組局部變數,就可以重複使用(因為它們被放在一個封閉區段中,有自己的scope)。
如:單一運算式:
minval = min ( val1, val2 ) + min ( foo (), foo () + 1);
分離的多個運算式:
minval_1 = min ( val1, val2 );
minval_2 = min ( val3, val4 );
Inline 函數對於封裝提供了一種必要的支援,可以有效存取封裝於class 中的nonpublic 資料。它同時也是C 程式中大量使用的#define (前置處理宏)的一個安全代替品——特別是如果宏中的參數有副作用的話。然而一個inline 函數如果被調用太多次的話,會產生大量的擴充碼,使程式的大小暴漲。