C 物件模型 第二章 建構函式語意學

2022-05-07 00:54:17 字數 4524 閱讀 5212

當編譯器需要的時候,default constructor會被合成出來,只執行編譯器所需要的任務(將members適當初始化)。

編譯器的出來是:如果乙個class a 內含乙個或者乙個以上 member class objects ,那麼class a 的每乙個 constructor 必須呼叫每乙個member classes 的default constructor 。編譯器會擴張已存在的constructors,在其中安插一些**,使得 user code在被執行之前,先呼叫(呼叫順序一member objects在class 的宣告次序一致)必要的 default constructors。(例如:乙個類中包含了兩個成員,乙個成員是乙個物件,而另乙個成員是built-in的變數,那麼編譯器會呼叫這個member物件的預設構造器,built-in變數就會置之不理。)

注意:這樣的編譯器預設合成構造器,是隱式的,如果類本身存在構造器,那麼編譯器會在程式需要的(可以理解為user code )**之前,安插必要的default constructor。

舉個例子,假設我們有以下三個classes:

class dopey ;

class sneezy ;

class bashful ;

以及乙個class snow_white:

class

snow_white

;

如果snow_white沒有定義default constructor,就會有乙個nontrivial constructor被合成出來,依序呼叫dopey、sneezy、bashful的default constructors。然而如果snow_white定義了下面這樣的default constructor:

//

程式設計師所寫的default constructor

snow_white::snow_white(): sneezy(1024

)

它將會被擴張為:

//

編譯器擴張後的default constructor

snow_white::snow_white():sneezy(1024

)

如果沒有定義建構函式,編譯器會產生乙個nontrivial constructor,先呼叫相應的base class's constructor,如果使用者自己定義了constructor,那麼編譯器將會安插呼叫所必要的default constructors的程式**到使用者自己定義的constructor中。(通俗地講:使用者已經定義了建構函式,但是沒有定義預設建構函式,那麼編譯器會把預設的東西加在進去,但不會重新寫乙個default constructor出來)。

下面兩種情況同樣需要合成default constructor:

擴充套件(constructor)操作會在編譯期間發生:

virtual base class的實現法在不同編譯器之間有很大差異,然而,每乙個實現的共同點在於必須使 virtual base class 在其每乙個 derived class object中的位置,能夠在執行期準備妥當。對於class所定義的每乙個constructor 編譯器都會安插那些「允許每乙個virtual base class 的執行期訪問操作」的碼。

以上四種情況,會導致「編譯器必須為未宣告constructor 的class 合成乙個default constructor 」,這只是編譯器(而非程式)的需要。它之所以能夠完成任務,是藉著「呼叫member object 或base class的default constructor 」或是「為每乙個object初始化其 virtual function 機制或virtual base class 機制」完成。至於沒有存在這四種情況而又沒有生命constructor的class 實際上是不會被合成出來的。

在合成的default constructor 中,只有base class subobjects(子物件)和member class objects會被初始化。所有其他的nonstatic data member ,如整數,整數指標,整數陣列等是不會被初始化的,這些初始化操作對程式是必須的,但對編譯器則並非需要的。

c++新手一般有兩個誤解:

有三種情況,會以乙個object的內容作為另一class object的初值。

1.最明顯的當然是對乙個object做明確的初始化操作。

2.當object被當做引數交給某個函式

3.當函式返回乙個class object。

這三種情況需要有 copy constructor。

如果class 沒有提供乙個 explicit copy constructor(顯示拷貝建構函式)時,當class object以「相同的另乙個object作為初值是,其內部是以所謂的default memberwise initialization方式完成的。也就是把每乙個內建的或派生的 data member(例如乙個陣列或指標)的值,從某個object拷貝乙份到另乙個object上,但不拷貝其具體內容。例如只拷貝指標位址,不拷貝乙份新的指標指向的物件,這也就是淺拷貝,不過它並不會拷貝其中member class object,而是以遞迴的方式實行memberwise initialization。

這種遞迴的memberwise initialization是如何實現的呢?

答案就是bitwise copy semantics和default copy constructor。如果class展現了bitwise copy semantics,則使用bitwise copy(bitwise copy semantics編譯器生成的偽**是memcpy函式),否則編譯器會生成default copy constructor。

那什麼情況下class不展現bitwise copy semantics呢?

有四種情況:

1.當class內含有乙個member class object,而這個member class 內有乙個預設的copy 建構函式[不論是class設計者明確宣告,或者被編譯器合成]

2.當class 繼承自 乙個base class,而base class 有copy建構函式[不論是class設計者明確宣告,或者被編譯器合成]

3.當乙個類宣告了乙個或多個virtual 函式

4.當class派生自乙個繼承串鏈,其中乙個或者多個virtual base class

下面我們來理解這四種情況為什麼不能使用bitwise copy,以及編譯器生成的copy constructor都幹了些什麼。

在前2種情況下,編譯器必須將member或者base class的「 copy constructor的呼叫操作」安插到被合成的copy constructor中。

第3種情況下,因為class 包含virtual function, 編譯時需要做擴張操作:

1.增加virtual function table,內含有乙個有作用的virtual function的位址;

2.建立乙個指向virtual function table的指標,安插在class object內。

所以,編譯器對於每乙個新產生的class object的vptr都必須被正確地賦值,否則將跑去執行其他物件的function了,其後果是很嚴重的。因此,編譯器匯入乙個vptr到class之中時,該class 就不在展現bitwise semantics,必須合成copy constructor並將vptr適當地初始化。

virtual base class的存在需要特別處理。乙個class object 如果以另乙個 virtual base class subobject那麼也會使「bitwise copy semantics」失效。

每乙個編譯器對於虛擬繼承的支援承諾,都是表示必須讓「derived class object 中的virtual base class subobject 位置」在執行期就準備妥當,維護「位置的完整性」是編譯器的責任。bitwise copy semantics 可能會破壞這個位置,所以編譯器必須自己合成出copy constructor。

這也就是說,拷貝建構函式和預設構造器一樣,需要的時候會進行構建,而並非程式設計師不寫編譯器就幫著構建。

下面四種情況必須使用初始化列表來初始化class 的成員:

1.當初始化乙個reference member時;

2.當初始化乙個const member時;

3.當呼叫乙個base class 的 constructor ,而它擁有一組引數(其實就是自定義的建構函式)時;

4.當呼叫乙個 member class 的 constructor,而它擁有一組引數時。

這裡總的需要留意兩點:

第乙個:member object的初始化,最好放到初始化列表裡面。若放置於構造器中,則會產生臨時的object0,初始化之,在做賦值運算給object,然後object0自行銷毀,期間耗時耗力。若置於初始化列表,則編譯器會在建構函式中,user code之前,呼叫object的建構函式,予以初始化。   

第二個:初始化列表的初始化次序。初始化次序和member在類中的宣告次序一致。相互關聯的member,需要十分留意初始化列表中,其中依賴的次序。解決的辦法:把其中一部分使用初始化列表初始化,而另一部分放置到建構函式中使用user code予以表達,這樣即便次序存在依賴,也會只「先執行合成的,再執行user的code」。

《深度探索C 物件模型》第二章 建構函式語意學

default constructor的構建操作 default constructors在需要的時候被編譯器產生。例 clas foo void foo bar 上述的 情況中,並不會生成乙個deafult constructor。需要注意的地方是 全域性的object內存在被啟用時會清0,而區域...

深度探索C 物件模型 第二章建構函式語意學

在使用c 時,常常會好奇或者抱怨,編譯器為我們做了什麼事呢?為什麼建構函式沒有為我初始化呢?為什麼我還要寫預設建構函式呢?2.1 default constructor 的構造操作 如果沒有宣告預設建構函式,編譯器會在需要的時候幫我們產生出來。為了避免在多個地方被需要導致重複,則編譯器將產生的建構函...

深度探索C 物件模型 之 建構函式語意學

explicit被引入c 是為了使程式設計師能夠制止 單一引數的constructor函式 被當做乙個conversion 運算子。有四種情況 1 帶有default constructor 的member classobject 2 帶有default constructor 的base clas...