天天看點

24、C++ Primer 4th 筆記,面向對象程式設計(2)

1、每個派生類對象包含一個基類部分。是以派生類對象也是基類對象。可以将派生類對象的引用轉換為基類子對象的引用,同理指針。

2、沒有從基類引用(或基類指針)到派生類引用(或派生類指針)的(自動)轉換。

3、編譯器不會自動将派生類型對象轉換為基類類型對象。

4、用派生類對象對基類對象進行初始化(或指派)時,将發生切割。

示例

5、派生類到基類的可通路性

    如果是 public 繼承,則使用者代碼和後代類(member functions of subsequently derived classes)都可以使用派生類到基類的轉換。如果類是使用 private 或 protected 繼承派生的,則使用者代碼不能将派生類型對象轉換為基類對象。如果是 private 繼承,則從 private 繼承類派生的類不能轉換為基類。如果是protected 繼承,則後續派生類的成員(the members of subsequently derived classes)可以轉換為基類類型。

    無論是什麼派生通路标号,派生類本身都可以通路基類的 public 成員,是以,派生類本身的成員和友元總是可以通路派生類到基類的轉換。

6、從基類到派生類的自動轉換是不存在的,需要派生類對象時不能使用基類對象。

7、編譯器确定轉換是否合法,隻看指針或引用的靜态類型。在這些情況下,如果知道從基類到派生類的轉換是安全的(指針或引用),就可以使用static_cast強制編譯器進行轉換。或者,可以用dynamic_cast申請在運作時進行檢查。

8、每個派生類對象由派生類中定義的(非static)成員加上一個或多個基類子對象構成;當構造、複制、指派和撤銷派生類型對象時,也會構造、複制、指派和撤銷這些基類子對象。

    構造函數和複制控制成員不能繼承,每個類定義自己的構造函數和複制控制成員。像任何類一樣,如果類不定義自己的預設構造函數和複制控制成員,就将使用合成版本。

9、每個派生類構造函數除了初始化自己的資料成員之外,還要初始化基類。派生類構造函數的初始化清單隻能初始化派生類的成員,不能直接初始化繼承成員,而通過将基類構造函數包含在構造函數初始化清單中間接初始化繼承成員。

    初始化順序為:首先初始化基類,然後根據聲明次序初始化派生類的成員。一個類隻能初始化自己的直接基類。

10、重構包括重新定義類層次,将操作和/或資料從一個類移到另一個類。派生類應通過使用基類構造函數尊重基類的初始化意圖,而不是在派生類構造函數體中對這些成員指派。

11、隻包含類類型或内置類型資料成員、不含指針的類一般可以使用合成操作,複制、指派或撤銷這樣的成員不需要特殊控制。具有指針成員的類一般需要定義自己的複制控制來管理這些成員。

    将類的指派操作符設為虛函數很可能會令人混淆,而且不會有什麼用處。

12、派生類析構函數不負責撤銷基類對象的成員。編譯器總是顯式調用派生類對象基類部分的析構函數。每個析構函數隻負責清除自己的成員。

    對象的撤銷順序與構造順序相反:首先運作派生析構函數,然後按繼承層次依次向上調用各基類析構函數。

13、虛析構函數

    删除指向動态配置設定對象的指針時,需要在釋放對象的記憶體之前運作析構函數清除對象。處理繼承層次中的對象時,指針的靜态類型可能與被删除對象的動态類型不同,可能會删除實際指向派生類對象的基類類型指針。

    如果删除基類指針,則需要運作基類析構函數并清除基類的成員,如果對象實際是派生類型的,則沒有定義該行為。要保證運作适當的析構函數,基類中的析構函數必須為虛函數。如果有虛成員函數,析構函數應該是虛的。

如果層次中根類的析構函數為虛函數,則派生類析構函數也将是虛函數。基類虛析構函數(複制,指派,析構)是三法則的一個例外。

即使析構函數沒有工作要做,繼承層次中的根類也應該定義一個虛析構函數。

14、構造函數不能定義為虛函數。構造函數是在對象完全構造之前運作的,在構造函數運作的時候,對象的動态類型還不完整。(虛函數用于動态聯編,是在運作時通過類型來确定函數的調用,而構造函數是在記憶體配置設定之前調用的,不可能知道是哪個類型,是以不能為虛函數)

構造派生類對象時首先運作基類構造函數初始化對象的基類部分。在執行基類構造函數時,對象的派生類部分是未初始化的。實際上,此時對象還不是一個派生類對象。

撤銷派生類對象時,首先撤銷它的派生類部分,然後按照與構造順序的逆序撤銷它的基類部分。

在這兩種情況下,運作構造函數或析構函數的時候,對象都是不完整的。為

了适應這種不完整,編譯器将對象的類型視為在構造或析構期間發生了變化。在

基類構造函數或析構函數中,将派生類對象當作基類類型對象對待。

    如果在構造函數或析構函數中調用虛函數,則運作的是為構造函數或析構函數自身類型定義的版本。

    無論由構造函數(或析構函數)直接調用虛函數,或者從構造函數(或析構函數)所調用的函數間接調用虛函數,都應用這種綁定。

要了解這種行為,考慮如果從基類構造函數(或析構函數)調用虛函數的派生類版本會怎麼樣。虛函數的派生類版本很可能會通路派生類對象的成員,畢竟,如果派生類版本不需要使用派生類對象的成員,派生類多半能夠使用基類中的定義。但是,對象的派生部分的成員不會在基類構造函數運作期間初始化,實際上,如果允許這樣的通路,程式很可能會崩潰。

15、如果在構造函數中調用一個虛函數,被調用的隻是這個函數的本地版本,虛機制在構造函數中不工作。因為虛函數機制使得可以調用派生類中的函數,而有可能派生類對象(的函數)還沒有初始化,這将出問題。 The state of the VPTR

is determined by the constructor that is called last. while all this series of constructor

calls is taking place, each constructor has set the VPTR to its own VTABLE. If it uses

the virtual mechanism for function calls, it will produce only a call through its own

VTABLE, not the most-derived VTABLE (as would be the case after all the

constructors were called).

    You should keep in mind that constructors and destructors are the only places

where this hierarchy of calls must happen 

16、Virtual or not. Inside a destructor, only the “local” version of the member

function is called; the virtual mechanism is ignored.

考慮到,虛析構函數調用派生類對象的虛成員函數,而有可能派生類對象的該成員函數它已經删除了,是以編譯器決定隻調用本地版本。 Notice that the same is

true(指構造函數不能為虛函數,而虛機制在析構函數中不起用途) for the

constructor and destructor, but in the constructor’s case the type information wasn’t

available, whereas in the destructor the information (that is, the VPTR) is there, but is

isn’t reliable.

參考

[2] 關于虛析構函數和構造函數讨論(virtual constructors)

<a href="http://blog.163.com/zhoumhan_0351/blog/static/39954227201032092854732/">http://blog.163.com/zhoumhan_0351/blog/static/39954227201032092854732/</a>

[3] 多态性和虛函數(Polymorphism&amp;Virtual Functions)

<a href="http://blog.163.com/zhoumhan_0351/blog/static/39954227201031995829238/">http://blog.163.com/zhoumhan_0351/blog/static/39954227201031995829238/</a>

繼續閱讀