深度探索C++對象模型-第二章

第二章 構(gòu)造函數(shù)語意學(xué)

2.1 Default Constructor的構(gòu)造操作

默認構(gòu)造函數(shù)在需要時被編譯器合成出來。

這里的需要分為:程序的需要 和 編譯器的需要塞蹭。

  • 程序的需要:即如果編譯器不合成出來,不會發(fā)生錯誤隐圾,但程序語意不是自己想要的必搞。
  • 編譯器的需要:即如果編譯器不合成出來,將發(fā)生錯誤的時候禽笑。

當(dāng)編譯器需要的時候,默認構(gòu)造函數(shù)會被合成出來蛤奥,并且只執(zhí)行編譯器所需要的任務(wù)佳镜。(也不會幫你賦初始值)

1.類中帶有 Default Constructor 的類對象成員

class Foo{ public: Foo(), Foo( int ) ... };
class Bar{ public: Foo foo; char *str; };

void foo_bar(){
    Bar bar;    
    
    if( str ){  }....
}

如果一個class沒有任何構(gòu)造函數(shù),但它內(nèi)含一個對象成員凡桥,而后者有默認構(gòu)造函數(shù)蟀伸,那編譯器需要為該class合成一個默認構(gòu)造函數(shù)(如下)。

Bar::Bar(){
    //被合成的默認構(gòu)造函數(shù)只會滿足編譯器的需要缅刽,因此不會為你初始化str
    foo.Foo::Foo(); 
}

若程序員定義了一個默認構(gòu)造函數(shù):

Bar::Bar(){
    str = 0;
}

現(xiàn)在程序的需求滿足了啊掏,但是編譯器還需要初始化foo。由于默認構(gòu)造函數(shù)已經(jīng)被顯式定義衰猛,編譯器沒法合成第二個迟蜜。

那么編譯器的行動是:

  • 如果一個class 內(nèi)含一個或者一個以上的類對象成員 ,那么class的每一個構(gòu)造函數(shù)必須調(diào)用每一個類成員的默認構(gòu)造函數(shù) 啡省。
  • 編譯器會擴張已存在的構(gòu)造函數(shù)娜睛,在其中安插一些代碼髓霞,使得在執(zhí)行用戶代碼之前,先調(diào)用(調(diào)用順序與對象成員在class 的聲明次序一致)必要的默認構(gòu)造函數(shù)微姊。

2.類繼承于帶有 Default Constructor 的基類

與前面道理相同:

  • 若沒有酸茴,則在類中沒有默認構(gòu)造函數(shù)分预,便合成兢交;
  • 若有,則在類中的默認構(gòu)造函數(shù)們在執(zhí)行用戶代碼之前笼痹,先調(diào)用(調(diào)用次序根據(jù)他們的聲明次序)所有基類的默認構(gòu)造函數(shù)配喳。

3.“帶有一個虛函數(shù)”的類

下面兩種情況,同樣需要合成默認構(gòu)造函數(shù):

  1. class 聲明(或繼承)一個虛函數(shù)坡氯;
  2. class派生自一個繼承串鏈摊灭,其中一個或者更多的 virtual base class(虛基類)田盈;

因為一個類中若存在虛函數(shù),那就少不了vptr與vtbl涧团。

因此在編譯期間發(fā)生:

  1. 一個虛函數(shù)表會被編譯器產(chǎn)生出來,內(nèi)放class 的虛函數(shù)們的地址经磅;

  2. 每一個類對象中泌绣,一個額外的pointer member(vptr)會被編譯器合成出來,內(nèi)含相關(guān)的虛函數(shù)表的地址预厌;

    【注】每一個類對象的意思是每一個有虛函數(shù)的類阿迈,就是說若基類與子類中都有虛函數(shù),那么在構(gòu)造函數(shù)中轧叽,都會執(zhí)行這兩步苗沧。

在合成的構(gòu)造函數(shù)中,編譯器會為每一個“帶有一個虛函數(shù)”的類(或者其派生類)的對象實例設(shè)定vptr的初值炭晒,并在其中放置虛函數(shù)表的地址待逞。

4.“帶有一個虛基類”的類

這一小節(jié)有點籠統(tǒng)。

Virtual base class的實現(xiàn)法在不同編譯器之間有很大差異网严。然而飒焦,<u>每一個實現(xiàn)的共同點在于必須使 虛基類 在其每一個 派生類 中的位置,能夠在執(zhí)行期準(zhǔn)備妥當(dāng)屿笼。</u>

對于class所定義的每一個constructor 牺荠,編譯器都會安插那些“允許每一個virtual base class 的執(zhí)行期存取操作”的代碼。

意思是:因為沒有辦法在編譯期確定 “虛基類中成員” 的實際偏移位置驴一,因此只能在構(gòu)造函數(shù)中加一些代碼休雌,合法化 “每一個虛基類的執(zhí)行期存取操作”。

總結(jié)

以上4種情況肝断,會導(dǎo)致“編譯器必須為未聲明構(gòu)造函數(shù)的類合成一個默認構(gòu)造函數(shù) ”杈曲,這只是編譯器(而非程序)的需要驰凛。

至于沒有存在這4種情況,而又沒有聲明構(gòu)造函數(shù)的class ,默認構(gòu)造函數(shù)不會被合成出來的担扑。

所有其他的非靜態(tài)變量 恰响,如整數(shù),整數(shù)指針涌献,整數(shù)數(shù)組等是不會被初始化的胚宦,這些初始化操作對程序是必須的,但對編譯器則并非需要的燕垃。

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

  1. 任何class 如果沒有定義默認構(gòu)造函數(shù)枢劝,就會被合成出來一個;
  2. 編譯器合成出來的默認構(gòu)造函數(shù)會明確設(shè)定 class 內(nèi)每一個data member的默認值卜壕;

2.2 Copy Constructor的構(gòu)造操作

有三種情況您旁,會以一個類的內(nèi)容作為另一類對象的初值。

  1. 最明顯的當(dāng)然是對一個object做明確的初始化操作轴捎;(X xx = x;
  2. 當(dāng)class object被當(dāng)做參數(shù)交給某個函數(shù)鹤盒;
  3. 當(dāng)函數(shù)返回一個class object;

1.Default Memberwise Initialization(逐個成員初始化)

如果class 沒有提供一個顯式拷貝構(gòu)造函數(shù)時侦副,當(dāng)class object以 “相同class的另一個object” 作為初值時侦锯,其內(nèi)部是以所謂的default memberwise initialization(逐個成員初始化)方式完成的。

【注】這里沒提拷貝構(gòu)造函數(shù)跃洛。

逐個成員初始化:也就是把每一個內(nèi)建的或派生的數(shù)據(jù)成員(例如一個數(shù)組或指針)的值率触,從某個object拷貝一份到另一個object上,但不拷貝其具體內(nèi)容汇竭。例如只拷貝指針地址葱蝗,不拷貝一份新的指針指向的對象,這也就是淺拷貝细燎,不過它并不會拷貝其中member class object两曼,而是以遞歸的方式實行memberwise initialization(就是再到這個member中,進行淺拷貝)玻驻。

memberwise initialization是如何實現(xiàn)的呢悼凑?

答案就是Bitwise Copy Semantics和default copy constructor。如果class展現(xiàn)了Bitwise Copy Semantics璧瞬,則使用bitwise copy户辫,否則編譯器會生成默認拷貝函數(shù)。

也就是說:判斷是否要合成拷貝構(gòu)造函數(shù)的標(biāo)準(zhǔn)嗤锉,是在于class是否展現(xiàn)出所謂的“bitwise copy semantics”(逐位拷貝語意)渔欢。

2.bitwise copy semantics(逐位初始化)

那什么情況下class不展現(xiàn)Bitwise Copy Semantics呢?有四種情況:

(等價于什么時候要用默認拷貝構(gòu)造函數(shù)瘟忱?)

  • 當(dāng)class內(nèi)含有一個類對象成員奥额,而這個類成員內(nèi)有一個默認的copy 構(gòu)造函數(shù)(不論是class設(shè)計者明確聲明苫幢,或者被編譯器合成);

  • 當(dāng)class 繼承自一個基類垫挨,而基類內(nèi)有copy構(gòu)造函數(shù)(不論是class設(shè)計者明確聲明韩肝,或者被編譯器合成);

  • 當(dāng)一個類聲明了一個或多個virtual 函數(shù)

  • 當(dāng)class派生自一個繼承串鏈九榔,其中一個或者多個virtual base class

在前2種情況下哀峻,編譯器必須將“類對象成員”或者“基類”的 copy constructor的調(diào)用操作 安插到 被合成的copy constructor中。

后兩種單獨拿出來講帚屉。

3.重新設(shè)定Virtual Table 的指針

第一章也提到谜诫,因為class 包含virtual function漾峡, 編譯時需要做擴張操作:

  1. 增加virtual function table攻旦,內(nèi)含有一個有作用的virtual function的地址;
  2. 創(chuàng)建一個指向virtual function table的指針生逸,安插在class object內(nèi)牢屋。

編譯器對于每一個新產(chǎn)生的class object的vptr都必須被正確地賦值,否則將跑去執(zhí)行其他對象的function了槽袄,其后果是很嚴重的(如圖2)烙无。因此,編譯器導(dǎo)入一個vptr到class之中時遍尺,該class 就不在展現(xiàn)bitwise copy semantics截酷,必須合成copy Constructor并將vptr適當(dāng)?shù)爻跏蓟?/p>

下圖這種同個類,若按照bitwise copy乾戏,沒有錯誤:

2-1.png

但如下圖這種迂苛,若按照bitwise copy,vptr有錯誤:

2-2.png

【注】圖中鼓择,雖然Bear對draw()和animate()函數(shù)重寫三幻,但虛函數(shù)重寫后也是虛函數(shù),因此在虛函數(shù)表里呐能。

4.處理Virtual Base Class Subobject

virtual base class的存在需要特別處理念搬。一個class object 如果以另一個 virtual base class subobject那么也會使“bitwise copy semantics”失效。

每一個編譯器對于虛擬繼承的支持承諾摆出,都是表示必須讓 “derived class object 中的virtual base class subobject 位置” 在執(zhí)行期就準(zhǔn)備妥當(dāng)朗徊,維護 “位置的完整性” 是編譯器的責(zé)任。Bitwise copy semantics 可能會破壞這個位置偎漫,所以編譯器必須自己合成出copy constructor爷恳。

這也就是說,拷貝構(gòu)造函數(shù)和默認構(gòu)造函數(shù)一樣骑丸,需要的時候會進行構(gòu)建舌仍,而并非程序員不寫編譯器就幫著構(gòu)建妒貌。

2.4 初始化列表

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

  1. 當(dāng)初始化一個reference member時;
  2. 當(dāng)初始化一個const member時铸豁;
  3. 當(dāng)調(diào)用一個base class 的 constructor 灌曙,而它擁有一組參數(shù)(其實就是自定義的構(gòu)造函數(shù))時;
  4. 當(dāng)調(diào)用一個 member class 的 constructor节芥,而它擁有一組參數(shù)時在刺。

編譯器會一一操作初始化列表,以適當(dāng)順序在構(gòu)造函數(shù)內(nèi)安插舒適化操作头镊,并且在任何用戶代碼之前蚣驼。

不過,初始化的順序是class members聲明次序決定的相艇,不是由初始化列表決定的颖杏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坛芽,隨后出現(xiàn)的幾起案子留储,更是在濱河造成了極大的恐慌,老刑警劉巖咙轩,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获讳,死亡現(xiàn)場離奇詭異,居然都是意外死亡活喊,警方通過查閱死者的電腦和手機丐膝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钾菊,“玉大人帅矗,你說我怎么就攤上這事〗岣浚” “怎么了损晤?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長红竭。 經(jīng)常有香客問我尤勋,道長,這世上最難降的妖魔是什么茵宪? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任最冰,我火速辦了婚禮,結(jié)果婚禮上稀火,老公的妹妹穿的比我還像新娘暖哨。我一直安慰自己,他們只是感情好凰狞,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布篇裁。 她就那樣靜靜地躺著沛慢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪达布。 梳的紋絲不亂的頭發(fā)上团甲,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音黍聂,去河邊找鬼躺苦。 笑死,一個胖子當(dāng)著我的面吹牛产还,可吹牛的內(nèi)容都是我干的匹厘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼脐区,長吁一口氣:“原來是場噩夢啊……” “哼愈诚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坡椒,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扰路,失蹤者是張志新(化名)和其女友劉穎尤溜,沒想到半個月后倔叼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡宫莱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年丈攒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片授霸。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡巡验,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碘耳,到底是詐尸還是另有隱情显设,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布辛辨,位于F島的核電站捕捂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斗搞。R本人自食惡果不足惜指攒,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僻焚。 院中可真熱鬧允悦,春花似錦、人聲如沸虑啤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至全闷,卻和暖如春绩蜻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背室埋。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工办绝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姚淆。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓孕蝉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腌逢。 傳聞我的和親對象是個殘疾皇子降淮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前述 本章的的主題是構(gòu)造函數(shù)語意學(xué),主要是挖掘編譯器對于“對象構(gòu)造過程”的干涉搏讶,以及對于“程序形式”和“程序效率”...
    野渡渡閱讀 578評論 0 0
  • 一個博客佳鳖,這個博客記錄了他讀這本書的筆記,總結(jié)得不錯媒惕∠捣裕《深度探索C++對象模型》筆記匯總 1. C++對象模型與內(nèi)...
    Mr希靈閱讀 5,587評論 0 13
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,519評論 1 51
  • 前言 這本書是之前京東做活動買的很多本書中的一本(主要閱讀時間是周末、每天早上起來吃早餐的時候妒蔚,以及下班回來時候穿挨,...
    ampire_dan閱讀 1,234評論 0 1
  • 上一章講過了關(guān)于類對象內(nèi)存分布,對于nostatic數(shù)據(jù)將會放在對象內(nèi)存空間中肴盏,static數(shù)據(jù)成員和nostat...
    babybus_hentai閱讀 416評論 0 0