1.c++
對虛擬基類的支援會導致一點額外的負擔,具體實現是在派生類物件中使用乙個指標來指向對應的虛擬基類,這個指標指向乙個**,**存放的是引導訪問虛擬基類子物件的資訊,例如其在派生類物件中的偏移量或者記憶體位址,這取決於編譯器實現。 2.
沒有任何資料成員的類,為了在記憶體中取得唯一的記憶體位址,所以會由編譯器插入乙個空的
byte
。但是這個優化僅僅是在沒有任何資料成員的類身上實現。(在
vs2017
中這個優化已經不存在了)
3.typedef
得到的別名應該寫到所有使用到這個別名的類成員宣告的前面,這樣可以避免發生跟類外的重名發生。 4.
5.靜態資料成員被引用在編譯器中會被直接替換為對
data segement
的資料的直接引用,一般不經過類物件引用。但是在
c++語法上存在乙個可能,那就是通過函式返回值引用靜態資料成員,這個時候單純地直接替換就會造成函式在實際上並沒有執行。對於這個情況的處理一般來說就分拆開兩部,先呼叫函式,然後再直接引用。
6.對靜態資料成員取指標得到的不是類成員的指標,而是該成員的資料型別的指標。
ps:類成員指標其實是其類內偏移值 7.
由於對非靜態資料成員進行訪問是通過類物件位址
+偏移量來定址的,而偏移量是編譯器就可以計算獲得的,所以訪問乙個非靜態資料成員效率跟普通地去訪問
c風格的結構體資料是一樣的,即使這個非靜態資料成員來自乙個只包含普通基類的繼承鏈。(繼承鏈中包含虛擬基類情況下這個結論不成立)
8.c++
通過在類物件中插入
vptr
來支援多型,這個
vptr
指向乙個
vtable
,當派生類物件被建立的時候,在建構函式裡面會根據實際情況重寫
vtable
的內容和
vptr
資料。
9.單一繼承由於結構簡單,一般記憶體布局上基類子物件放在派生類物件的開頭,所以兩者的位址應該是一樣,這是天然的多型,只需要對該指標進行不同的解析就好。多重繼承由於內部存在多個不同的基類子物件,自然基類子物件的位址也是不一樣的,要對這樣的派生類執行多型,一般是編譯器把相關的基類子物件指標操作內在地使用頭位址
+offset
的方式進行。
10.虛擬繼承一般的實現方法:把含有虛擬基類的類資料分成不變區域性和共享區域性,不變區域性一般是指非虛擬基類資料成員的部分,為了實現共享區域性的共享,一般就是間接訪問,以保證不同情況的虛擬繼承能有相同的布局。
11.虛擬繼承為了實現共享區域性的共享,一般都是在類內部儲存乙份共享區域性的資料,在各個派生類物件內部持有乙份訪問該共享區域性資料的指標。但是當繼承鏈中有超過乙個虛擬基類的話,那麼需要持有的指標也需要相應地增加。為了解決這個問題,一般有兩種常用的策略,一種是需要再增加一層間接訪問,建立乙個
virtual base class table
,類同於
virtual function table
,這樣的話無論虛擬基類有多少個,只需要增加
virtual base class table
的長度就可以了,派生類持有的
virtualbase class table
指標就只要乙個。第二種是把這個
virtual base class table
建在virtual function table
的負向座標。
上面所做的一切努力都是為了讓虛擬繼承的記憶體布局能穩定一些,不會因為繼承鏈過於複雜而複雜程度大幅增加。由於多型只發生在指標或者引用出現的場合,所以一般的類物件訪問虛擬基類成員的時候,不需要這麼複雜的間接定址,可以直接在編譯期就計算出成員位址。
12.對類資料物件取指標得到的是該物件在類內部的偏移值,一般來說這個偏移值會比實際情況大
1,這是為了區分指向第乙個資料成員的指標和空指標0。
13.靜態成員函式沒有
this
指標,它的出現是為了解決訪問靜態成員變數的封裝問題,具體例子就是單例模式.
對靜態成員函式取址得到的是實際的記憶體位址。
class a
return *_instance;
}private:
static a*_instance;
a();
};14.
單一繼承對於虛函式的處理比較簡單,由於只有乙個單一的基類,在編譯期就可以把派生類中的
vptr
在類中的位址確定好。所以相對比較簡單。
15.多重繼承在支援虛函式上主要面臨的問題是如果通過指向非第一基類的指標呼叫虛函式,這個時候必須要在每乙個非第一基類內部儲存乙份
vptr
,這個vptr
指向的vtable
與第一基類的
vptr
指向的vtable
的內容有所不同,這是因為這個時候需要動態調整
this
指標來保證在呼叫派生類的函式的時候不會使用了非第一基類的
this
,應該使用派生類的
this
。非第一基類的
vtable
要另外儲存
offset
資訊來計算出派生類的真正位址。
16.虛擬繼承跟單一繼承的區別在於虛基類的子物件在派生類物件中只能通過
vtable
的乙個offset
值來訪問,這樣有助於統一派生類與其他類的記憶體布局的一致性。
17.乙個指向非靜態非虛擬成員函式的指標,需要繫結於某個類物件的位址上才能被正確地執行,因為呼叫中隱含地傳入類物件位址作為
this
指標。由於靜態成員函式不需要
this
指標,所以對應的指標型別應該是一般的「函式指標」,而不是「指向成員函式的指標」。
18.對虛函式取址得到的是該函式位址在
vtable
裡面的索引值。這裡有個問題,如果類裡面有兩個函式原型一樣僅僅是函式名不一樣的函式,乙個是虛函式,乙個是普通的成員函式,那麼在執行
ptr->*pmf
的時候怎麼確定是呼叫普通的成員函式還是虛函式?對於這個問題各個編譯器的處理方法不一樣
19.由於inline
函式需要在呼叫點原地展開**,所以有可能增加源程式的大小。
20.避免濫用虛函式機制,對於在派生類中極小可能被重寫的函式,盡量不宣告為虛函式,這樣會增加呼叫成本。另外虛函式盡量避免宣告為
const
,因為這樣會限制派生類中的函式修改資料成員的能力。最後就是避免把基類的析構函式宣告為純虛函式,一般宣告為虛函式。
《深度探索C 物件模型》讀書筆記1
建構函式 1,在合成的預設建構函式中,只有成員物件,和基類子物件會被初始化,其他非靜態成員變數都不會被初始化。2,對於乙個class沒有乙個使用者定義的建構函式,那麼將會有預設的建構函式被隱式的宣告出來,有四種情況會造成編譯器會對乙個沒有沒有定義建構函式的類,自動合成乙個勾走函式 1,帶有預設建構函...
《深度探索C 物件模型》讀書筆記(5)
純虛函式 在設計抽象基類時,需要注意以下幾點 1 不要將destructor宣告為pure virtual function 如果將destructor宣告為pure virtual function,則設計者一定得定義它。因為每乙個derived class destructor會被編譯器加以擴充...
《深度探索C 物件模型》讀書筆記(6)
物件的構造和解構 一般而言,我們會把object盡可能放置在使用它的那個程式區段附近,這樣做可以節省不必要的物件產生操作和銷毀操作。全域性物件 全域性物件的靜態初始化策略包括以下幾個步驟 1 為每乙個需要靜態初始化的物件產生乙個 sti 函式,內含必要的constructor呼叫操作或inline ...