C 物件模型學習筆記2 建構函式語意學

2021-08-29 23:49:52 字數 3968 閱讀 3296

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...