default建構函式操作:
首先需要說明:
帶來的第乙個問題,編譯器什麼時候合成預設建構函式(nontrivial)?答案是編譯器需要的時候,而不是程式需要的時候。
1)class foo ;
class bar ;
void foo_bar()
此時編譯器就會合成default 建構函式;類似
inline bar::bar()
編譯器合成的預設建構函式只滿足自己需要,不滿足程式需要。
我們需要定義建構函式滿足程式需要:類似
bar::bar()
編譯器的行動:如果class a內含乙個或者乙個以上的member class objects,那麼每個constructor必須呼叫每乙個member class的預設建構函式。(在已有的constructors之前安插一些**)
// c++偽**
bar::bar()
2)同樣的面對「帶有default constructor」的base class的 時候也會採用類似上面的行為來對待建構函式。
3)「帶有乙個virtual function」的class
對於virtual function編譯期間編譯器會做兩個事情:
1. 產生virtual function table,其中放入class的virtual function的入口位址。
2. 每乙個class object,會被編譯器合成乙個額外的vptr指向這個virtual table。
如果程式沒有提供建構函式,編譯器需要合成乙個建構函式來新增**做上面的兩件事情。如果程式提供了乙個以上的建構函式,編譯器就會在建構函式user code之前插入一些**來完成需要做的事情
4)帶virtual base class的class
虛基類的引入我們都知道主要在於解決多重繼承時,基類可能被多次繼承,虛基類提供乙個基類給派生類
class x ;
class a : pulic virtual x {};
class b : pulic virtual x {};
class c : pulic a, public b {};
void foo(const a* pa)
編譯器必須改變執行訪問操作的這些**,是x::i可以到執行期才決定下來。編譯器可能類似策略:
void foo(const a* pa)
然而_vbcx這類東西需要在object構造期間產生,所以對於class定義的每個constructor,編譯器會安插一些**來做「允許每個virtual base class 的執行期存期操作」**,如果沒有宣告任何constuctors編譯器必須合成乙個default constructor。
以上是對於編譯器合成default constructor的四種情況,容易的錯誤理解:
1)任何class如果沒有定義default construtor就會合成乙個
2) 合成的default constructor會顯示的設定"class內每乙個data member的預設值"
預設拷貝建構函式(default copy constructor
)、預設賦值運算子(
operator =
)和預設析構函式,是
c++類中的六大特殊成員函式中的三個。三者同時遵循乙個原則:「一榮俱榮、一損俱損」。如果三者其中的任意乙個被顯示定義了(defined
)那麼三者必須都被顯式定義。當果三者之一被程式設計師呼叫但未沒有被顯式宣告時,編譯器會隱含的實現這三個特殊成員函式。當用乙個類物件去初始化另乙個類物件時,需要用到拷貝建構函式;當用乙個類物件去設定另乙個類物件時,需要用到賦值運算子。
拷貝建構函式與賦值運算子都遵循「default memberwise assignment&initialization」原則,即對類中的每乙個資料成員進行依次複製,但是通常編譯器只採用
bitwise copy
方式複製(這樣能夠提高效率)。例如,對於只含有
pod成員資料的簡單類,
bitwise copy
方式綽綽有餘。但是以下幾種情況比較特殊:
1) 當class
內含有乙個
member object
時,並且後者的
class
中宣告了乙個
copy constructor時;
依照「default memberwise assignment&initialization」原則,初始化
member object
時,需要編譯器呼叫
member class
的拷貝建構函式,如果類中沒有顯式定義拷貝建構函式,就需要編譯器構造,來呼叫成員類的拷貝建構函式。
2) 當類的基類中至少有乙個含有拷貝建構函式時;
同樣依照「default memberwise assignment&initialization」原則,需要依次構造所有的基類成員,如果沒有顯式定義預設拷貝建構函式,那麼這部分工作就有編譯器來完成。
3) 當類中宣告乙個或多個virtual functions時;
4) 當類的派生鏈中有乙個或多個virtual base class時;
這裡由於虛函式的機制,需要初始化vbtl
和vptr
。這部分需要編譯器來完成(本身虛擬機制就是從編譯器角度來實現的)。
以上幾種情況如果程式設計師未顯式定義拷貝建構函式,編譯器會自動完成拷貝建構函式的實現,用以滿足編譯器的需要,不過此時編譯器合成的版本不在以bitwise的拷貝方式來完成。
以上四種情況都能容易理解,因為其進行bitwise copy的時候會發生錯誤,所以不展現bitwise copy semantics(需要特別注意的是1,2點中,需要base class和類成員變數所在class具有copy constructor)
關於第三種情況:
當編譯器匯入乙個vptr到class之中的時候,class就不在展現bitwise semantics,編譯器需要合成乙個copy constructor將vptr適當的初始化。
例子:
class zooani
class bear: public zooani
bear y;
bear w = y;
此時w的vptr被設定為指向bear class的virtual table。因此也就是所將y的vptr拷貝給w是vptr安全的.
zooani z = y;
此時z的vptr不能直接指向bear的virtual table(也就是不能直接通過將y的vptr"bitwise copy"到z的vptr。
上述例子表達的是合成的copy constructor會顯示設定object的vptr指向zooani的virtual table,而不是直接通過拷貝。
第四種情況:
virtual base class表示的必須讓「derived class object中virtual base class subobject位置」在執行期準備妥當,然而bitwise copy可能破壞這個位置
class zooani {}
class raccoon: virtual public zooani {}
class redpanda: public raccoon {}
redpanda red;
raccoon rac = red; // 此時如果直接進行bitwise copy,virtual base class subobject位置發生破壞。
另外需要注意的是,合成copy constructor中,如int,指標等nonclass members也會被複雜。
例子:
class word
合成的copy constructor
inline word ::word( const word& wd)
從編譯器編譯連線角度,以上四種情況下如果未定義拷貝建構函式,編譯器為了編譯工作的順利進行,會自定義拷貝建構函式;從程式設計者角度,如果模擬較複雜(例如含有指標、引用等),單單依靠編譯器定義預設拷貝建構函式,程式是無法達到預定效果的,所以此時往往需要程式設計師顯式定義出自己的拷貝建構函式。
程式轉化語意學:
1)返回值的初始化:
x bar()
問題:返回值如何從區域性物件xx中拷貝?
編譯器的轉化
void bar(x& __result)
x xx = bar();編譯器轉化為
x xx;
bar(xx);
2) 編譯器層面named return value(nrv)優化:(有興趣可以在g++通過優化層次上開啟nrv和不開啟做下對比)
void bar(x& __result)
3)使用著層面的優化
x bar()
寫成 // 這種寫法更多的是考慮了效率和不是優先抽象化
x bar()
編譯器的轉化:
void bar(x& __result)
深淺拷貝基礎知識:memberwise copy(深拷貝) 和 bitwise copy(淺拷貝)
memberwise copy:
在初始化乙個物件期間,基類的建構函式被呼叫,成員變數被呼叫,如果它們有建構函式的時候,它們的建構函式被呼叫,這是乙個遞迴的過程.
bitwise copy:原記憶體拷貝,
例子,給定乙個物件object,它的型別是class base.物件object占用10位元組的記憶體,位址從0x0到0x9.如果還有乙個物件objecttwo,型別也是class base.那麼執行objecttwo( object);如果使用bitwise拷貝語義,那麼將會拷貝從0x0到0x9的資料到objecttwo的記憶體位址,.也就是說bitwise是位元組到位元組的拷貝。
C 建構函式語意學 預設建構函式
在 class 中,若程式設計師沒有為該 class object 定義 default constructors,則編譯器會根據需要產生乙個 implicit default constructor,該 implicit default constructor 被認為是 trivial 無用的 那...
深度探索C 物件模型 之 建構函式語意學
explicit被引入c 是為了使程式設計師能夠制止 單一引數的constructor函式 被當做乙個conversion 運算子。有四種情況 1 帶有default constructor 的member classobject 2 帶有default constructor 的base clas...
C 物件模型學習筆記2 建構函式語意學
jerry schwarz,iostream函式庫建構師,曾為了讓cin能夠求得乙個真假值,定義了conversion運算子operator int 這樣使用者寫出 if cin 語句,就會很方便。但當使用者想要寫cout intval時,不小心寫成了cin intval 結果編譯器沒有報錯,哈哈,...