1. 程式人生 > 實用技巧 >深度探索C++物件模型 個人總結 第二章 建構函式語意學

深度探索C++物件模型 個人總結 第二章 建構函式語意學

建構函式語意學

explicit使得能夠制止“單一引數的constructor”被當做conversion運算子

2.1 Default Constructor的構造操作

defaultconstructors在需要的時候被編譯器產生出來,被誰需要?做什麼事情?

程式需要時並不會合成一個defaultconstructor,只有當編譯器需要它時才會合成,被合成出來的defaultconstructor只執行編譯器所需的行動

“帶有DefaultConstructor”的MemberClassObject

編譯器需要為一個有defaultconstructor的內含memberobject合成出一個defaultconstructor,如果class A內含一個或一個以上的memberclass object,那麼每個class A的每一個constructor必須呼叫每一個memberclasses的defaultconstructor。編譯器會擴張已存在的constructors,在其中安插一些程式碼,使得usercode被執行之前,先呼叫defaultconstructor。如果有多個classmemberobject都需要進行初始化,C++語言要求以“memberobjects在class中的宣告順序”呼叫各個constructors。

defaultconstructor、copyconstructor、destructor、assignmentcopyoperator都是以inline方式完成。如果函式太複雜不適合做成inline就會合成一個explicitnon-inlinestatic例項

“帶有DefaultConstructor”的BaseClass

如果一個子類的基類帶有預設建構函式, 那麼在合成子類的建構函式時, 會在其中插入對基類的預設建構函式會的呼叫程式碼, 這個程式碼在成員的預設建構函式呼叫程式碼之前. 即先初始化基類, 再按宣告順序初始化子 類成員.

“帶有一個VirtualFunction”的Class

另外兩種情況也需要合成預設建構函式:

  1.class宣告(繼承)一個virtualfunction

  2.class派生自一個繼承串鏈,其中有一個或更多的virtualbaseclasses

下面兩個擴張行為會在編譯期間發生:

  1.一個virtualfunctiontable (vtbl)會被編譯器產生出來,內放class的virtualfunctions地址

  2.在每一個classobject中,一個額外的pointermember(vptr)會被編譯器合成出來,內含相關之classvtbl的地址

編譯器為每一個baseclass(或其derivedclass)object的vptr設定初值,放置適當的virtualtable地址。對於class所定義的每一個constructor,編譯器會安插一些程式碼來做這樣的事,對於那些未宣告constructor的classes,編譯器會為它們合成一個defaultconstructor,以便正確地初始化每一個classobject的vptr。

“帶有一個VirtualBaseClass”的Class

必須使virtualbaseclass在其每一個derivedclassobject中的位置,能夠於執行期準備妥當

在合成的defaultconstructor中,只有baseclasssubobjects和memberclassobjects會被初始化,所有其他的nonstaticdatamember(如整數、整數指標、整數陣列等等)都不會被初始化,如果程式需要一個“把某指標設為0”的defaultconstructor,那麼提供它的人應該是程式設計師

2.2Copy Constructor的構造操作

以一個object的內容作為另一個classobject的初值:

  1.對一個object做顯式的初始化

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

  3.當函式傳回一個classobject時

大部分情況下,當一個classobject以另一個同類例項作為初值,copyconstructor會被呼叫

DefaultMemberwiseIntialization

  如果class沒有提供一個explicitcopyconstructor,,必要時編譯器會產生出來(“必要”意指class不展現bitwisecopysemantics時)

  拷貝手法是以所謂的defaultmemberwiseintialization完成的,也就是每一個內生的或派生的data member(例如一個指標或一個數組的值),從某個object拷貝到另一個object身上。不過不會拷貝其中的memberclassobject。而是以遞迴的方式施行memberwiseinitialization

BitwiseCopySemantics(位逐次拷貝)

  會拷貝每一個位(bit)

什麼時候不要位逐次拷貝:

  1.當類內含一個成員物件, 該成員物件中聲明瞭一個copy 建構函式(不論是顯式宣告或是編譯器合成)

  2.類繼承的基類中存在一個建構函式(不論是顯式宣告或是編譯器合成)

  3.類聲明瞭一個或多個虛擬函式

  4.當類派生自一個繼承串連, 其中有一個或多個虛基類時

前兩種情況編譯器必須把member或baseclass的拷貝建構函式呼叫操作安插到被合成的拷貝建構函式中

重新設定VirtualTable的指標

  當一個classobject以另一個相同的classobject作為初值,都可以直接靠”bitwisecopysemantics“完成

  當一個baseclassobject以其derivedclass的object內容作初始化時,其vptr複製操作也必須保證安全,vptr不可以設定指向derivedclass的virtualtable

  所以說合成出來的baseclass copyconstructor會顯式設定object的vptr指向baseclass的virtualtable,而不是直接從右手邊的classobject中將其vptr現值拷貝過來

處理VirtualBaseClassSubobject

  virtualbaseclass的存在需要特別處理。一個classobject如果以另一個有virtualbaseclasssubobject的object作為初值,那麼也會使”bitwisecopysemantics“失效,這是因為編譯器必須讓”derivedclassobject中的virtualbaseclasssubobject位置“在執行期就準備妥當。”Bitwisecopysemantics“可能會破壞這個位置,所以編譯器必須在它自己合成出來的copyconstructor中作出仲裁。編譯器所產生的程式碼(用以呼叫virtual baseclass的defaultconstructor、將derivedclass的vptr初始化,並定位出derivedclass中的virtual baseclasssubobject)被安插在derivedconstructor間,成為其”先頭部隊“

  如果企圖以一個derivedclassobject作為base classobject的初值,編譯器必須判斷”後續當程式設計師企圖存取baseclassobject的virtual baseclass subobject時是否能夠正確地執行“,為此編譯器必須合成一個copyconstructor,安插一些程式碼以設定virtualbaseclasspointer/offset的初值,對每個members執行必要的memberwise初始化操作,以及執行其他的記憶體相關工作。

  

2.3程式轉化語意學(ProgramTransformation)

顯式的初始化操作

1.重寫每一個定義,其中初始化操作會被剝離(”定義“是指”佔用記憶體“的行為)

2.class的copyconstructor呼叫操作會被安插進去

引數的初始化

把一個class object當作引數傳給一個函式或是作為一個函式的返回值,相當於以下形式的初始化操作:

X xx = arg;其中xx代表形式引數或返回值,而arg代表真正的引數值

返回值的初始化

返回值如何從一個區域性物件中拷貝過來

1.首先加上一個額外引數,型別是class object的一個reference,用來放置被”拷貝建構“而得的返回值

2.在return指令之前安插一個copyconstructor呼叫操作,以便將欲傳回之object的內容當做上述新增引數的初值

在使用者層面做優化

X bar(const T&y,const T&z)
{
  X xx;
      //......以y和z來處理xx
  return xx;
}
//由於這會要求xx被”memberwise“地拷貝到編譯器所產生的_result之中 X bar(const T&y,const T&z) {    return X (y,z);//定義一個constructor }

在編譯器層面做優化

NamedReturnValue(NRV)優化:需要加上一個inlinecopyconstructor,提供了重要的效率改善。

飽受批評的原因:

  1.優化由編譯器默默完成,而他是否真的被完成,並不十分清楚

  2.一旦函式變得比較複雜,優化也就比較難以施行。許多程式設計師主張應該以”特殊化額constructor“策略取代之

一般而言面對”以一個classconstructor作為另一個classconstructor的初值“的情形,語言允許編譯器有大量的自由發揮的空間。其利益當然是導致機器碼產生有明顯的效率提升。缺點則是你不能夠安全地規劃你的copyconstructor的副作用,必須視其執行而定

Copy Constructor:要還是不要

copy constructor的應用迫使編譯器多多少少對程式程式碼作部分優化,尤其是當一個函式以by value的方式傳回一個class object,而該class有一個copy constructor(或定義或合成)時,無論在函式的定義還是在使用上將導致深奧的程式轉化。此外,編譯器將實施NRV優化。

注意正確使用memset()memcpy(),它們都只有在classes不含任何由編譯器產生的內部membersvptr時才能有效執行

2.4成員們的初始化隊伍(MemberInitializationList)

必須使用memberinitializationlist:

  1.當初始化一個referencemember時

  2.當初始化一個constmember時

  3.當呼叫一個baseclass的constructor,而它擁有一組引數時

  4.當呼叫一個memberclass的constructor,而它擁有一組引數時

”初始化順序“和”initializationlist中的專案排列順序“之間的外觀錯亂,會導致下面意想不到的危險

初始化列表中的專案被放在顯示宣告程式碼(explicit user code)之前