說明:
? <u>不是很清楚的點</u>,用下劃線。
? 解答,用斜體;
? 重點,用粗體加粗;
第四章 Function 語意學(xué)
4.1 Member的各種調(diào)用方式
1. Nonstatic Member Functions
實際上編譯器是將member function被內(nèi)化為nonmember的形式,經(jīng)過下面轉(zhuǎn)化步驟:
- 給函數(shù)添加額外參數(shù)——this;
- 將對每一個nonstaitc data member的存取操作改為this指針來存取;
- 將member function 重寫成一個外部函數(shù)。對函數(shù)名采用mangling 處理,使之成為獨一無二的語匯;
2. Virtual Member Functions(虛成員函數(shù))
將
? ptr->f(); //f()為虛成員函數(shù)
內(nèi)部轉(zhuǎn)化為
? (*ptr->vptr[1])(ptr);
其中:
- vptr表示編譯器產(chǎn)生的指針,指向virtual table。它被安插在每一個聲明有(或繼承自)一個或多個virtual functions 的class object 中。
- 1 是virtual table slot的索引值,關(guān)聯(lián)到normalize()函數(shù)。
- 第二個ptr表示this指針。
3. Static Member Functions (靜態(tài)成員函數(shù))
Static Member Functions的主要特性就是它沒有this指針
次要特性:
- 不能被聲明為const、volatile、virtual;
- 不能夠直接存取其class中的非靜態(tài)數(shù)據(jù)成員;
Static Member Functions由于缺乏this指針,因此差不多等同于非成員函數(shù)。
如果取一個static member function 的地址,獲得的是其在內(nèi)存的位置(也就是地址),而不是一個指向“class member function”的指針,如下:
&Point::count();
會得到一個數(shù)值,類型是:
unsigned int(*)();
而不是:
unsigned int(Point::*)();
4.2 Virtual Member Functions
C++中,多態(tài)表示以“一個public base class 的指針(或reference),尋址出一個derived class object”。
為了在執(zhí)行期調(diào)用正確的虛函數(shù),在編譯時期:
- 可以在每一個多態(tài)的class object身上增加兩個members:
- 一個字符串或數(shù)字,表示class的類型;
- 一個指針vptr,指向某一個表格,表格中持有程序的虛函數(shù)們的執(zhí)行期地址;
- 每一個虛函數(shù)都被指派一個表格索引值;
那么,表格中的虛函數(shù)們地址如何被購建起來?
在C++中,虛函數(shù)們可經(jīng)由其class object被調(diào)用,可以在編譯時期獲知。此外,這一組地址是固定不變的,執(zhí)行期不可能新增或替換之。由于程序執(zhí)行時,表格的大小和內(nèi)容都不會改變,所以其建構(gòu)和存取皆可以由編譯器完全掌控。不需要執(zhí)行期的任何介入。**
而執(zhí)行期要做的,只是在特定的虛函數(shù)表slot中激活虛函數(shù)。
每一個class 只會有一個virtual table,每一個table 含有對應(yīng)的class object中所有active virtual functions 函數(shù)實體地址。這些active virtual function 包括:
- 這個class 所定義的函數(shù)實體(包括改寫一個可能存在的base class virtual function函數(shù)實體)。
- 繼承自base class 的函數(shù)實體(不被derived class改寫)
- 一個pure_virtual_called()。
繼承過程中,virtual table的三種可能性:
- 繼承base class 所聲明的virtual functions的函數(shù)實體。正確地說,是該函數(shù)實體的地址會被拷貝到derived class的virtual table相對應(yīng)的slot之中。
- 使用自己的函數(shù)實體。這表示它自己的函數(shù)實體地址必須放在對應(yīng)的slot之中。
- 可以加入一個新的virtual function。這時候virtual table 的尺寸會增大一個slot放進這個函數(shù)實體地址。
編譯時期設(shè)定virtual function的調(diào)用:
ptr->z();
- 一般而言,我并不知道ptr 所指對象的真正類型。然而可以經(jīng)由ptr 可以存取到該對象的virtual table。
- 雖然我不知道哪個Z()函數(shù)實體被調(diào)用,但知道每一個Z()函數(shù)地址都被放置某一個slot(如slot 4)的索引。
這樣我們就可以將
ptr->z();
轉(zhuǎn)化為:
(*ptr->vptr[4])(ptr);
唯一一個在執(zhí)行期才能知道的東西是:slot4所指的到底是哪一個class object的z()函數(shù)實體。
多重繼承下的 Virtual Functions
在多重繼承中支持virtual functions,其復(fù)雜度圍繞在第二個及其后面的base class 上,以及“必須在執(zhí)行期調(diào)整this 指針”這一點。
一般規(guī)則是,經(jīng)由指向“第二或后繼base class 的指針”來調(diào)用derived class virtual function。調(diào)用操作連帶的“必要的this指針調(diào)整”操作,必須在執(zhí)行期完成。
在多重繼承下,一個派生類內(nèi)含n-1個額外的虛函數(shù)表,n表示其上一層基類的個數(shù)(因此,單一繼承將不會有額外的虛函數(shù)表)?!救羰请p重繼承,那么就會有一個與派生類共享的虛函數(shù)表】
如下圖:

【注】
- base1的虛函數(shù)表是主要表格,base2的是次要表格;
- 函數(shù)后面有加*,表示這不是真實的函數(shù)實例,是需要加this指針指向真正的函數(shù)實例。
4.3 函數(shù)的效能
non-member、static member或non-static member函數(shù)都被轉(zhuǎn)換為完全相同形式,所以三者效率完全相同。
4.4 指向Member Function的指針
取一個non-static data member的地址,得到的結(jié)果是該member在class 布局中的bytes位置(再加1),所以它需要綁定于某個class object的地址上,才能夠被存取。
取一個non-static member function的地址,如果該函數(shù)是non-virtual,則得到的是內(nèi)存的真正地址,然后這個值也是不完全的,也需要綁定于某個class object的地址上,才能夠調(diào)用函數(shù)。(所有的non-static member function都需要對象的地址,以參數(shù)this指出)
支持“指向Virtual Member Function”之指針
對于一個virtual function,其地址在編譯時期是未知的,所能知道的僅是virtual function在其相關(guān)之virtual table的索引值,也就是說,對于一個virtual member function 取其地址,所能獲得的只是一個索引值。
4.5 Inline Function
形參:
- 傳入?yún)?shù),直接替換;
- 傳入常量,連替換都省了,直接變成常量;
- 傳入函數(shù)運行結(jié)果,則需要導(dǎo)入臨時變量,以避免重復(fù)求值;
局部變量:
一般而言,inline函數(shù)中的每一個局部變量都必須被放在函數(shù)調(diào)用的一個封閉區(qū)段中,擁有一個獨一無二的名稱。
如果一次性調(diào)用N次,就會出現(xiàn)N個臨時變量……程序的體積會暴增,如下:
minval = min( val1, val2 ) + min( foo(), foo()+1 );
因此要以分離的多個式子被調(diào)用。