c++在物件的構造時做了什麼手腳?——《深度探索c++物件模型》閱讀筆記之一
前言
《深度探索c++物件模型》是《c++ primer》的作者Lippman所寫的一本書,Lippman從自己參與開發cfront的經歷出發,深度剖析了c++中物件的相關特性是如何被實現的。
這本書相對其他的各種c++特性解讀,用法,最佳實踐的書來說,相對艱深晦澀,其面對的也不是剛入門的C++ 學習者,而是想要了解C++在幕後做了什麼的人。
這本書的作用應當是消除程式設計師腦海中的不確定性,揭開C++物件表面的魔法。一個物件,其定義, 建立,呼叫方法,析構等等,編譯器有為你做了什麼,又沒做什麼,如果想要真正的瞭解C++物件的工作機理,閱讀這本書是大有裨益的。
本筆記是為了記錄一些閱讀中的知識點而設立,同時也是消化知識的一個過程。
以下內容是對書中第二章《The semantics of constructors》的消化梳理
1. 預設建構函式
對於簡單的class中,我們經常略過對預設建構函式的宣告和定義,編譯器會照顧好一切,我們只要呼叫就行。
但是如果說究其根本,我們必須問,編譯器真的為每個Class建立了預設建構函式嗎?
為何這麼問,因為拋去所有的物件特性的外衣,它仍然是一個函式,真的需要對每一個例項都呼叫一次這個函式嗎?
假如說我依照c語言中的習慣定義了以下這個sturct:
1 struct point{ 2 int x; 3 int y; 4 };
我只想像在c語言一樣,將這個只看做是幾個基本型別變數的組合來使用,那麼每次建立一個point例項,都要呼叫一次建構函式,這也太瘋狂了!
c++的設計者們也是這樣想的,有些時候,預設建構函式是完全不需要的,只需要分配好記憶體即可,這時我們說這是一個trivial的預設建構函式,只是觀念上存在,但是編譯器並不會生成相關程式碼。
編譯器只在以下特定的幾種情況下會合成一個預設建構函式
1.1 成員中有擁有預設建構函式的物件
1 class bar{ 2 //有預設建構函式 3 }; 4 5 class foo { 6 bar member; 7 };
當一個類中的成員是一個擁有預設建構函式的類時,編譯器就無法袖手旁觀了,它必須給這個類加上一個合成的預設建構函式,以呼叫其成員的預設建構函式。
當有多個這樣的成員類時,合成的預設建構函式會按照宣告順序進行呼叫成員的預設建構函式。
如果說我們已經自己定義了預設建構函式呢? 那麼編譯器會向我們定義的函式前插入程式碼來初始化成員物件
1.2 繼承自有預設建構函式的基類
1 class base{// 有建構函式}; 2 class derived :public base{};
這種情況是下,因為繼承的性質使然,派生類必然需要呼叫父類的預設建構函式,所以編譯器一定要合成一個預設建構函式
1.3 帶虛擬函式的類
c++中動態多型的展現,需要通過虛擬函式,其實現是通過虛擬函式表來做到的。存在虛擬函式的類中,在例項化時加入一個指向虛擬函式表的指標,做這件事必須通過建構函式來實現。
1.4 繼承自虛基類的類
虛基類也是動態的,在編譯期無法確定其記憶體位置,所以必然也需要有指標指向,在物件建立時,對這個指標進行操作來確定虛基類的記憶體位置。 那麼為了實現這個功能,必然需要合成預設建構函式
2. 拷貝建構函式
對於拷貝建構函式,不少人會有迷糊的感覺,覺得編譯器會給每一個類合成一個,但是實際遠不是這樣的,
因為對於部分情況,拷貝建構函式是沒有任何必要的,只需要進行所謂的 bitwise copy就行,即把物件對應的二進位制內容原封不動的複製一份就行
但在一些情況下,編譯器會壓抑bitwise copy, 為類合成一份拷貝建構函式
2.1 當成員有擁有一個拷貝建構函式時
同預設建構函式類似,一個成員擁有拷貝建構函式(無論時被編譯器合成的,還是自己顯式定義的)時,該類就不能進行bitwise copy了,需要合成拷貝建構函式。
否則就無法呼叫成員的拷貝建構函式
2.2 繼承自擁有拷貝建構函式的基類
和預設建構函式類似,拷貝時必須呼叫基類的拷貝建構函式,所以必然需要合成
2.3 類擁有虛擬函式時
在這種情況,拷貝建構函式需要設定虛擬函式表,不能理解的話考慮用一個派生類物件初始化基類物件,這時必然需要設定成員中的虛擬函式表指標重新指向基類的虛擬函式表。
2.4 類派生自虛基類時
和預設建構函式類似,需要保證其虛基類指標的正確設定,必然需要合成拷貝建構函式
本章中還談論了 NRV優化其利弊和形式,以及面試官很喜歡的初始化列表的問題。這些暫時就不寫了。