深度探索C 物件模型 第二章讀書筆記

2021-09-02 05:47:09 字數 3198 閱讀 3740

1.如果設計者提供多個constructors,但其中都沒有default constructor呢?編譯器會擴張現在的每一次constructors,將「用以呼叫所有必要之default constructor」的程式**加進去。它不會合成乙個新的default constructor,因為其他由user所提供的constructors存在的緣故。如果同時亦存在著「帶有default constructor」的member class objects,那些default constructors也會被呼叫--在所有base class constrcutor都被呼叫之後。

2.當乙個類有虛函式時下面兩個擴張操作會在編譯期間完成:

① 1個virtual function table會被編譯器產生出來,內放class的virtual functions的位址。

當用指標或者引用呼叫虛函式時:widget->filp(); 會被編譯器改寫為*(widget->vptr[1])(widget)  

1代表flip在virtual table中的固定索引

widget代表要交給「被呼叫的某個flip函式例項」的this指標

為了讓著機制發揮功效,編譯器必須為每乙個widget(或其派生類的)object的vptr設定初始值,放置適當的virtual table位址。對於class所定義的每乙個constructor,編譯器都會安插一些**來做這樣的事情。對於那些未宣告任何constructor的class,編譯器會為它們合成乙個default constructor,以便正確地初始化每乙個class object的vptr。

3.當類有虛基類時,編譯器也會生成乙個額外的虛基類指標,以指向其虛基類。根據不同的編譯器實現機制,虛基類指標可能指向的是乙個跟虛函式表類似的虛基類表,也可能將虛基類的位址放在虛函式表中,當使用虛函式表時,索引為正時呼叫的是虛函式,索引為負時使用的是虛基類。總而言之,虛基類指標的作用就是必須讓所指向的虛基類位址在執行期間確定下來。在編譯期間由於指標可能指向的真正型別無法確定,因為虛基類在不斷繼承的過程中偏移位址會發生改變的緣故,所以虛基類指標才顯得那麼重要。所以建構函式需要初始化以便在執行期可以正確找到那個虛基類的位址。

例如 類a有乙個虛基類x,x有乙個成員a。類a有乙個派生類b。a* p;p可能指向類b也可能指向類a,而虛基類x在類a與類b中的位置是不同的,所以p->a這樣呼叫虛基類的成員時,編譯器會變成p->vbtr->a。通過乙個虛基類指標來追蹤虛基類x,這就是建構函式在這裡的作用,正確設定好虛基類的位置。

4.default constructor和copy constructor在必要的時候才由編譯器合成出來。必要指的是class不展現bitwise copy semantics

是否展現bitwise copy semantics也可以參考建構函式的哪四種情況。基類和成員類物件很好理解,主要是後兩種。當類有虛函式時,我們需要正確初始化vptr,而使用bitwise copy semantics不能保證。所以編譯器需要合成乙個copy constructor來正確初始化vptr。

bear yogi;

bear winnie=yogi;拷貝建構函式可以使得winnie正確初始化,vptr也指向yogi的vptr。

但是當zooanimal franny=yogi;這會發生切割,如果施行了bitwise copy semantics 的話 會使得franny的vptr指向yogi的vptr,而多型是無法通過物件施行的(編譯器會拒絕)。所以會有乙個copy constructor 合成出來,使得yogi的基類部分的vptr賦值給winnie。

最後一種情況則是 當含有乙個虛基類時:例如racoon有乙個虛基類,redpanda繼承racoon

racoon rocky;

racoon litter_critter=rockey;這樣使用bitwise就行了

而如果 redpanda litter_red;

racoon litter critter=litter_red;由於虛基類在不同子類中的偏移位置不同,所以編譯器需要合成一些**來正確地初始化虛基類指標。在例子中,虛基類在litter_red中的偏移與在litter_critter中不一致,所以進行賦值時需要進行調整。這步由編譯器合成的copy constructor完成。

5.引數的初始化:

把乙個類物件當做引數傳給乙個函式(或者作為乙個函式的返回值)相當於下面這種形式的初始化操作:

x xx=arg;

若已知這個函式:

void foo(x x0);

下面這種呼叫方式:

x xx;

foo(xx);

區域性變數初始化的方式有兩種:①拿xx來拷貝構造乙個臨時物件,將這個臨時物件以bitwise的方式拷貝到x0上

② 直接拷貝構建,將實參構建在形參上。

返回值的初始化:

x bar()

x  xx;

return xx;

bar()的返回值如何從區域性物件xx中拷貝過來?

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

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

這裡會看你是如何使用這個函式再看編譯器會如何操作:

如果 是 x x=bar();  

函式將會變為void bar(x& result)

x xx;

xx.x::x();

result.x::xx(xx);//編譯器加上的拷貝構建操作

return;                        

x x=bar(); 將會變為 x xx;bar(xx);第一步並未呼叫建構函式

而當你這樣去使用時:bar().memfunc()會產生乙個臨時物件。在區域性物件返回前先拷貝給臨時物件,然後再對這個臨時物件進行操作。

還有一種在使用者上可做的優化就是:

當你返回乙個物件時  直接return  x(1,2);//忽略引數

這樣我們將會省去乙個區域性物件的建立,以及編譯器加的那步拷貝構建的操作,而將物件直接構建在等待被拷貝構造的函式外的物件、x x=bar();  

第三種優化是nrv優化:

x bar()

x  xx;

return xx;

} 既然我們是要將xx拷貝給函式外的物件,我們為什麼不直接讓類外的物件代替這個xx,在函式內就執行完所有的操作,而且類外的物件是以傳引用的方式進入函式的。

6.初始化列表的操作總是放在顯式的使用者**之前。

深度探索C 物件模型筆記 第二章

explicit關鍵字能夠制止單參建構函式被當作型別轉換運算子.編譯器的隱式操作只是為了滿足編譯器本身的需求,而不是程式本身,乙個被編譯器隱式生成的預設建構函式,多數情況下對於程式本身來說是無用的.如果乙個類沒有任何建構函式,但類中的乙個非內建型別成員變數有預設建構函式,那麼這個類被隱式生成的預設建...

《深度探索C 物件模型》第二章 建構函式語意學

default constructor的構建操作 default constructors在需要的時候被編譯器產生。例 clas foo void foo bar 上述的 情況中,並不會生成乙個deafult constructor。需要注意的地方是 全域性的object內存在被啟用時會清0,而區域...

深度探索C 物件模型 第二章建構函式語意學

在使用c 時,常常會好奇或者抱怨,編譯器為我們做了什麼事呢?為什麼建構函式沒有為我初始化呢?為什麼我還要寫預設建構函式呢?2.1 default constructor 的構造操作 如果沒有宣告預設建構函式,編譯器會在需要的時候幫我們產生出來。為了避免在多個地方被需要導致重複,則編譯器將產生的建構函...