C 物件模型 第五章 構造 解構 拷貝 語意學

2022-05-07 00:54:17 字數 4024 閱讀 8832

c++的建構函式可能內帶大量的隱藏碼,因為編譯器會擴充每乙個建構函式,擴充程度視 class 的繼承體系而定。一般而言編譯器所做的擴充操作大約如下:

所有虛基類成員建構函式必須被呼叫,從左到右,從最深到最淺:

如果類中含有 vptr,那麼它們必須被設定初值,指向適當的 vtbl。

如果有乙個成員物件沒有出現在初始化列表之中,呼叫它的預設建構函式(如果有的話)。

記錄在成員初始化列表中的資料成員的初始化操作會放進建構函式本身,並以成員的宣告順序為順序。(普通資料成員是最後才構造的)。

在虛擬繼承體系下,如果不是 most-derived 的類,那麼其建構函式會被編譯器加入乙個bool __most_derived 引數,並賦值為 false,建構函式中判斷該 bool 值,如果為 false 就不會構造虛基類,一直壓抑虛基類的建構函式直到最外層也就是most-derived 類才構造,保證虛基類值構造一遍。

繼承體系中,每乙個建構函式中呼叫的函式會以靜態方式決議之,因此即便某個基類和子類在建構函式中呼叫名字相同的函式,那麼呼叫的也是基類的函式,子類此時還未構造出來。

拷貝賦值函式(copy assignment operator,即operator=)不具備 most-derived 特性!因為它沒有像建構函式一樣的執行列表,並且賦值函式是可以被取得位址的(就是說客戶可見的意思)。所以在虛繼承中,virtual base class subobject 會在不同層級的物件中多次被執行拷貝複製函式。

如下圖的繼承關係:

如果vertex3d構造的時候,必然呼叫point3d的建構函式,同時呼叫vertex的建構函式,然而這兩個類都要必須呼叫point2d的建構函式,這是不合理的。取而代之的是應該在vertex3d的建構函式中直接對point2d初始化。這樣就需要vertex3d再呼叫point3d或者vertex的建構函式的時候傳遞乙個bool引數__most_derived,即「是否是最後一層繼承關係」,然後point3d或者vertex的建構函式根據這個bool變數決定是否構造point2d。

總結為一句話:virtual base class constructor,只有當乙個完整的class object被定義出來時,它才會被呼叫。如果object只是某個完整的object的suboject,他就不會被呼叫。

在base class constructor呼叫操作之後,但是在程式設計師提供的**或是「member initialization list中所列的members初始化操作」之前編譯器對vptr進行初始化。這個過程就像想象的那樣:乙個pvertex物件會先成為乙個point2d物件。乙個point3d物件、乙個vertex物件和乙個vertex3d物件,最後才成為乙個pvertex物件。

乙個建構函式的真實步驟可能如下:

1.在derived class constructor 中,「所有virtual base classes」及「上一層base class」的constructor會被呼叫。

2.上述完成後,物件vptr(可能多個vptrs)被初始化,指向相關的virtual table(可能多個表)

3.如果有member initialization list 的話,將在constructor體內擴充套件開來。這必須在vptr被設定之後才進行,以免有乙個virtual member function被呼叫。

4.最後,執行程式設計師所提供的**。

當設計乙個class,並以乙個class object 指定另乙個class object時,有三種選擇:

1.什麼都不做,實施預設行為。

2.提供乙個explicit copy assignment operator。

3.明確拒絕乙個class object指定給另乙個class object。

如果選擇第三點,那麼只要將copy assignment operator宣告為private,並且不提供其定義即可。把它設定為private,我們就不在允許於任何地點(除了member function和該class的friend之中)做賦值操作。不提供其函式定義,則一旦某個member function或friend企圖影響乙份拷貝,程式在鏈結是失敗。

對於第二點,只有在預設行為所導致的語意不安全或不正確時,我們才需要設計乙個copy assignment operator。

乙個class對於預設的copy assignment operator,在以下情況,不會表現出bitwise copy語意: 

當class內含乙個member object,而其class有乙個copy assignment operator時。

當乙個class的base class有乙個copy assignment operator時。

當乙個class宣告了任何virtual function(我們不一定要拷貝右端class object的vptr,因為它可能是乙個derived class object)時。

當class繼承自乙個virtual base class(不論此base class有沒有copy operator)時。

在虛擬繼承情況下,copy assignment opertator會遇到乙個不可避免的問題, virtual base class subobject的複製行為會發生多次,與前面說到的在虛擬繼承 情況下虛基類被構造多次是乙個意思,不同的是在這裡不能抑制非most-derived class 對virtual base class 的賦值行為。

事實上,copy assignment operator 在虛擬繼承情況下行為不佳,需要小心地設計和說明。因為 copy assignment operator 缺乏乙個member assignment list(也就是平行與member initialization list 的東西),因此編譯器沒有辦法壓抑上一層的base class 的copy operators被呼叫,導致在虛擬繼承情況下,derived class 將對virtual base class 進行多重拷貝。c++語言說:我們並滅有規定那些代表virtual base class 的subobjects 是否該被「隱喻定義(implicitly defined)的copy assignment operator」指派(賦值,assign)內容一次以上。因此建議盡可能不要允許乙個virtual base class的拷貝操作,甚至可能的話,不要在任何virtual base class 中宣告資料。

vptr在constructor何時被初始化?在base class constructors呼叫操作之後,但是在程式設計師**的碼或是初始化列表中所列的members初始化操作之前。

1.destructor的函式本身首先被執行。

2.如果class擁有member class objects,而後擁有destructor,那麼它們會以宣告順序的相反順序被呼叫。

3.如果object內帶乙個vptr,則現在被重新設定,指向適當的base class virtual table。

4.如果有任何直接的(上一層)nonvirtual base classes 擁有destructor ,它們會以宣告順序相反順序呼叫。

5.如果有任何virtual base classes 擁有destructor,而當前討論的這個class 是最尾端的class,那麼它們會以其原來順序相反順序被呼叫。

1.純虛基類盡量不要定義資料成員,如果定義了就需要在建構函式或其他成員函式設定初值,不過這通常是一種不好的設計。

2.純虛基類的純虛函式可以在派生類中以靜態方式呼叫。

3.宣告了純虛析構函式就一定得定義它,為什麼?因為每乙個派生類析構函式都會被編譯器擴充套件,以靜態呼叫方式呼叫其每乙個虛基類以及上一層基類的析構函式。因此,只要缺乏任何乙個基類析構函式的定義,就會導致鏈結失敗。

4.如果某個函式其函式定義內容不與型別有關,不會被後繼派生類改寫,不要定義為虛函式。因為它的非虛函式實體是 inline,所以不使用 virtual 更能提高效率。

5.虛函式中盡量不要使用 const,因為它可能被很多次呼叫,可能需要修改資料成員。

6.對於 pod 型別的類,編譯器不會為它生成什麼函式,因為那是無關痛癢的。包括 delete 該型別的指標,也不會觸發 constructor。(除開你自己定義的情況)。

深度探索C 物件模型筆記 第五章

inline和define 內聯函式在編譯時展開,而巨集是由預處理器對巨集進行展開 內聯函式會檢查引數型別,巨集定義不檢查函式引數 所以內聯函式更安全。巨集不是函式,而inline函式是函式 巨集在定義時要小心處理巨集引數,可能出現二義性 一般情況是把引數用括弧括起來 const常量有資料型別,而巨...

第五章 構造 析構 拷貝語意學

class abstract base protected 最好提供建構函式 abstract base char mumble value 0 mumble mumble value char mumble class concrete derived public abstract base 抽...

第五章 適用物件

第一節 字元型別 單個的字元 char public class main 輸出3 字元 char 和整數 int 可以互相轉換 大小寫轉換 1.大寫 小寫 public class main 2.小寫到大寫 public class main 第二節包裹型別 基礎型別 boolean char i...