天天看點

深度探索C++對象模型 【第四章1】

1:靜态成員函數(static member function)不可能是做到以下兩點:

  • 不可能直接存取非靜态成員變量
  • 不可能被聲明為const

2:類成員函數發展曆史

  • 原始的class隻支援非靜态成員函數
  • 20世紀80年代加入了虛函數機制
  • 靜态成員函數在1987年最後加入,并由cfront2.0實作

3:C++的設計準則:非靜态成員函數至少和非成員函數有着相同的效率。這就産生了内在的成員函數被内化為非成員函數的過程

  • 改寫函數的原型(signature),安插一個和額外的參數到成員函數中,以提供一個存取管道,使得對象得以調用該函數,改參數被稱為“this指針”
  • 将成員函數中的每一個“非靜态成員資料的存取操作”改變為通過this指針來存取
  • 最後将成員函數重寫為一個外部函數,再經由編譯器的mangling處理,變成了程式中獨一無二的存在

4:名稱的特殊處理,一般而言,成員的名稱之前都會加上class的 名稱,變得獨一無二。編譯器還會有跟更加優化的處理,假設在基類和派生類中都聲明了一個同名的變量,那麼該如何區分它們呢?

  • ival_3BAR 和 ival_3FOO,編譯器将BAR和FOO兩個類中的同名變量分别命名,FOO繼承自BAR這樣在派生類中就不會引起沖突。
  • 由于成員函數還可以被重載,是以就需要更廣泛的name_mangling手法,以提供獨一無二的名稱。

5:在函數前聲明“extern C”會壓制編譯器的非成員函數的name_mangling。

6:C++目前的name_mangling編碼方法并未統一。目前編譯器未将其中的内部實作名稱展示給程式員,cfront決議非虛函數方式為func1_fv(),Func_fl()。

7:對于虛成員函數(virtual member functions),使用對象顯示調用虛函數将和非虛函數的決議方式相同;但使用指針或者引用調用時,其決議方式會發生改變

A a;
A *b = &a;
b->func1();//func1為虛函數
(*b->vptr[1])(b);//内部轉化結果,1是vbtl的索引值,指向虛函數func1;第二個b為this指針;vptr[0]中存的可能是RTTI
           

vptr是由編譯器産生的指針,指向vbtl,它會出現在每一個含有虛函數類的類對象中。

8:将一個虛成員函數聲明為inline,将會帶來極大的效率利益。

9:靜态成員函數将會被轉化為一般的非成員函數調用,其主要特性就是沒有this指針,不需要對象就能夠使用(但是也可以通過對象調用),但其不能被聲明為const/volitale/virtual。

10:如果擷取一個靜态成員函數的位址,将會獲得其在記憶體中的位置,也就是其位址,且其位址的類型不是一個指向class member function的指針,而是一個非成員的指針。

11:在C++中,多态最初的意思就是使用一個基類指針尋址出一個派生類對象的意思,1993年RTTI被引入,産生了積極多态,我們可以在執行期查詢一個多态的指針或者引用。

12:識别一個class是否支援多态,唯一适當的方法時就是看它是否有任何的虛函數。

  • 編譯期:找到vbtl,找到虛函數的位址
  • 執行期:激活虛函數(純虛函數如果被意外的調用,通常的做法是直接結束掉這個程式)

13:單一繼承中的虛函數機制行為良好,但虛拟繼承和多重繼承中,就沒這麼簡單了。對于多重繼承來說,Base1和Base2共同派生一個Derived.

  • Base1 *p1 = new Derived;  //p1不需要調整this指針,因為Base1為最左端的基類,它已經指向派生類對象的起始處。虛函數表中存放的是真正的虛函數指針。
  • Base2 *p2 = new Derived; // p2需要調整this指針,其虛函數表中需要相關的thunk位址

在多重繼承之下,一個派生類中内含n-1個虛函數表,n表示其上一層的基類個數。對于上例來說,會有兩個虛函數表被編譯器産生,一個主要執行個體,與Base1共享;一個次要執行個體,與Base2有關。這兩個執行個體将會以外部對象的方式産生,兩個表格的命名有差異。

14:虛拟繼承中的虛函數機制由于太過于複雜,是以本書中Lippman也沒有解釋。隻留下了一個建議:不要在虛基類中定義一個非靜态資料成員。

15:對于成員函數的效能,有以下的測試結果

  • inline函數有明顯的提升,消除了額外的時間負擔,也提供了程式優化的額外機會
  • 每多一層繼承,虛函數的執行時間就有明顯的增加

繼續閱讀