jerry schwarz,iostream函式庫建構師,曾為了讓cin能夠求得乙個真假值,定義了conversion運算子這個故事告訴我們,編譯器背後可能會隱式地做點什麼。operator int()
。這樣使用者寫出:
if (cin)
語句,就會很方便。但當使用者想要寫
cout << intval
時,不小心寫成了cin << intval
; 結果編譯器沒有報錯,哈哈,它的解析語義是:把cin轉為整型,然後左移intval
。jerry最後用
operator void *()
取代operator int()
。
classa;
intfoo()
有些人以為這裡物件oa的成員a值是零,其實它是個未定義值,初始化它是程式設計師的責任。這裡編譯器並不會為a產生任何建構函式。
所以需要區別是編譯器需要?還是程式的需要(使用者需要)?
另外,即使編譯器在它需要合成的情況下產生了乙個預設建構函式,該函式也不會處理初始化的內容,仍然需要使用者自行指定。
帶有default constructor的member class object
很容易理解,類成員物件在定義時,因為它有預設建構函式,那麼它需要進行相應的構造,但何時呼叫呢?肯定是在本物件建立的時候,那麼本物件得有乙個建構函式去構造它們。
即使該函式已經具有乙個建構函式了,編譯器也需要在使用者顯式的語句前,安插**來初始化。
eg:obja.a::a()
當有多個成員物件都需要呼叫時,呼叫順序則為宣告的順序。
帶有default constructor的base class
容易理解,基類的內容建立時需要按照它指定的去初始化。
當使用者已經有好多個ctor,但沒有提供default ctor. 編譯器會擴充套件當前的每乙個ctor,將需要執行的工作放到每個ctor裡,但不會再合成乙個新的default ctor.
帶有virtual function的class
容易理解,初始化函式需要為產生的物件設定虛表指標
帶有virtual base class的class
跟3類似,需要為物件的布局初始化跟virtual base class相關的東西。
至於沒有存起那四種情況而又沒有宣告任何ctor的classes,我們說它們擁有的是implicit trivial default constructors,它們實際上並不會被合成出來。
一定要注意,合成出來的預設建構函式只會初始化編譯器認為的必要的部分(基類物件,類物件),其他資料成員並不會被初始化。
1. 任何類只要沒有定義預設建構函式,就會被合成出來乙個。
2. 編譯器合成出來的預設建構函式,會為類內的每乙個成員設定預設值。
以乙個物件的內容作為另乙個物件的初值 (進行初始化)
對乙個物件做明確的初始化操作, eg: a obja1 = obja0
乙個物件被當作引數交給某個函式時,eg:foo(obja);
函式返回乙個物件時
跟建構函式一樣,拷貝構造也分為trivial和non-trivial兩種。只有non-trivial的實體才會被合成出來。決定乙個拷貝構造是否為trivial的標準在於class是否展現出所謂的"bitwise copy semantics"。
bitwise copy semantics(位逐次拷貝語意),說白了就是按照位元組寬度的拷貝。有四種情況,乙個class不展現出bitwise copy sema.
類成員物件宣告有或被編譯器合成有乙個copy ctor時,這些物件需要顯式定義拷貝構造(或編譯器提供),說明位元組複製無法滿足需求。
基類有copy ctor時 (顯式或者編譯器合成)
存在virtual函式
存在virtual繼承的基類
前兩種情況中,編譯器必須將member或base class的copy ctor呼叫操作安插到被合成的copy ctor中。
沒有拷貝建構函式的成員,將繼續使用bitwise copy。
重點分析後兩種情況,
1. 重新設定virtual table指標
對於同乙個類的物件進行拷貝構造,bitwise copy問題不大,對應vptr指向的是同乙個類。
但可能存在情況是,b b = d (d繼承b),這時候會發生切割,因為b實際的大小已經為b類的大小了,其 實際的虛表也需要指向b類本身的,
如果簡單地進行bitwise copy,這時候的vptr就指向d,就錯了!
細想下也不允許是d的vptr,假設有個虛函式foo,d的foo函式解析的是d大小的物件,而此時的b只有b類大小。而ref/point不會有這個問題,因為底層的大小還是沒有變,該是d還是d。
2. 處理virtual base class suboject
類似的概念,同乙個類物件不會有什麼問題,但如果子類的物件去構造父類的物件,需要保證父類物件裡vbase point/offset是對的。
顯式初始化操作會有兩個必要的程式轉化階段:先重寫每乙個定義,剝除其中的初始化操作
,然後安插class的copy ctor呼叫操作。
eg:
x x1
(x0)
=>
x x1; 定義被重寫,初始化操作被刪除 (僅留下記憶體占用 去掉初始化操作)
x1.x::
x(x0) copy ctor
引數初始化/返回值初始化 編譯器可以做一些函式簽名引用重寫,rvo等優化。
foo
(a);
a a;
foo(a)
=>
a t;
t.a::
a(a)
; 呼叫copy ctor
foo(t); 重寫foo
()函式呼叫,使用暫時物件。需要重寫介面為引用效率更高
a foo()
=>
void
foo(a &ret)
=>
void
foo(a &ret)
節省了一次copy ctor.
有時候如果編譯器的bitwise copy實現太差,可以考慮自己顯式定義copy ctor,用memcpy等優化函式。 haha, 有這樣的場合麼???
必須使用初始化列表的場合:
初始化乙個reference member時;
初始化乙個const member時;
呼叫乙個base class的constructor,而它擁有一組引數時;
呼叫乙個member class的constructor,而它擁有一組引數時。
class
word
};
這裡的ctor會先產生乙個臨時的string obj並初始化string(0),然後以assignement運算子指定給_name, 然後臨時變數再銷毀。
word()
:_name(0
)
即: _name.string::string(0);
編譯器會對initialization list一一處理並可能重新排序,以反映出members的宣告次序,它會安插一些**到constructor內,並置於任何explicit user code之前。list專案順序是由members宣告順序決定的,不是由init list裡排列順序決定。
class
x}
i值未定義,因為按照宣告次序,i在j前, i = j 時 j 還未賦值為val。
x::x(
int val):j
(val)
符合預期,因為init list先執行。 C 物件模型學習筆記五 拷貝建構函式語義
測試驗證編譯器在什麼情況下會幫助我們合成出拷貝建構函式,及編譯器合成出來的拷貝建構函式又要幹什麼事情?拷貝建構函式語義 在下面情況下,如果我們不寫自己的拷貝建構函式,編譯器就會幫助我們合成出拷貝建構函式來。1 如果乙個類t沒有拷貝建構函式,但是含有乙個類型別ctb的成員變數m ctb。該型別ctb含...
C 物件模型 拷貝建構函式語義
目錄引例 如果乙個類a沒有拷貝建構函式,但是含有乙個類型別ctb的成員變數m ctb。該型別ctb含有拷貝建構函式,那麼當 中有涉及到類a的拷貝構造時,編譯器就會為類a合成乙個拷貝建構函式。如果乙個類ctbson沒有拷貝建構函式,但是它有乙個父類ctb,父類有拷貝建構函式,當 中有涉及到類ctbso...
深度探索C 物件模型 之 建構函式語意學
explicit被引入c 是為了使程式設計師能夠制止 單一引數的constructor函式 被當做乙個conversion 運算子。有四種情況 1 帶有default constructor 的member classobject 2 帶有default constructor 的base clas...