摘要
簡介
編譯器可能會有隱含的動作,如對 overload operator 的誤判. 因為 compiler 會自動去尋找最符合其意義的來解釋你的意圖.
constructor
以下是各種 default constructor 的建構情形. 一般若 class 沒有宣告 constructor, 那麼 compiler 會在需要時為他建立一個簡單的
constructor, 有四種狀況:
1. class 沒有 constructor, 但成員之中有 member object, 而該 member object 有 default constructor
時, compiler 必定會為前者合成一個 default constructor.
假設
class Dopey {...}; class Sneezy {...}; class Bashful {...}; class Snow_White { public: Dopey dopey; Sneezy sneezy; Bashful bashful; private: int mumble; };
Snow_White 沒有 default constructor, 那麼 compiler 會為他合成一個 default constructor,
依序喚起 Dopey, Sneezy, Bashful 的 default constructor. 若 Snow_White 定義了 constructor
如下:
Snow_White::Snow_White:sneezy(1024) { mumble=2048; }
則 compiler 會擴充為
Snow_White::Snow_White();sneezy(1024) { dopey.Dopey::Dopey(); sneezy.Sneezy::Sneezy(1024); bashful.Bashful::Bashful(); mumble=2048; }
2. 若 class 繼承一個帶有 default constructor 的 class, 則 compiler 會為這個 class 合成一個 constructor,
這個 constructor 只呼叫 parent class 的 constructor.
又若有多個 constructor, 但卻沒有 default constructor 時, compiler 會擴張各 constructor, 以便去呼叫必要的
default constructor. 但卻不會去合成一個新的 default constructor.
3. 若 class 有 virtual function 的時候, compiler 會在 default constructor 中把 vtable
初始化,以便讓子類別能正確呼叫到 virtual function 所對應的 function.
4. virtual base class, 各 compiler 的實作方法不同,但共通點都是要使 virtual base class 在每個 derived
class object 中的位置,能夠在 runtime 時準備妥當.例如:
class X { public: int i; }; class A: public virtual X { public: int j; }; class B: public virtual X { public: double d; }; class C:public virtual X {public: int k;}; //無法在編譯時決議(resolve)出 pa->X::i void foo( const A* pa) { pa->i=1024; } int main() { foo(new A); foo(new C); //... }
所以, compiler 必須改變"執行存取動作"的程式碼,使得 X::i 能延遲到 runtime 時才決定下來.
以上四種情況都會使 compiler 必須為未宣告 constructor 的 class 合成 default constructor, 這些被 compiler
合成出來的東西, 在 C++ Standard 中稱為 implict nontrival default constructor.
除了這四種情況之外, compiler 不會合成任何 constructor.
另外在合成的 default constructor 中, 只有 base class object 和 member class object 會被初始化,
其他 nonstatic data member,如: int, int*, int array 等都不會.
新手誤解:
1.任何 class 若沒有 default constructor, 就會自動合成一個.
2. compiler合成的 default constructor 會明白設定"class內每個 data member 的預設值".
copy constructor
有三種情況會以一 object 內容作為另一 class object 的初值.
- string bb; string aa=bb;
- object 被當作參數交給函式時.
- 函數傳回值是 object 時.
當沒有提供 copy constructor 時,會直接把 member 一個個地複製到要複製的 object 上, 如:
class string { char *str; int len; };
則
string noun("book"); string verb=noun;
實際上是
verb.str=noun.str; verb.len=noun.len;
此處的 copy constructor 不等於 copy assignment (operator=)!!
default constructor 和 copy constructor 在必要的時候才用 compiler 產生出來.
請注意 member 若有 pointer 時,那麼預設會把指標指過去,就會有潛在的指標問題!
此種情況應宣告 explicit copy constructor 來解決此問題.如:
class string { public: string(const char *); string(const string&); };
一般 copy 會有四種情況, class 不展現出 "bitwise copy semantics"(即上述狀況):
- 1.class 內含一個 member object 而後者有 copy constructor 時.
- 2.class衍生自一個 base class, 而 base class 有 copy constructor 時.
- 3.class宣告一個或多個 virtual functions.
- 4.當 class 衍生自一個繼承串鏈,其中有一個或多個 virtual base classes.
第三種情況需考慮到之前第一章所提到的 virtual table.
因此若父類別有 animate() 和 draw()這兩個 virtual function,而子類別增加 dance(), 則不能
childclass cc1; parentclass parent1=cc1;
會造成 virtual table 被切掉,因為 parent1 的 virtual table 根本就沒有 dance.若 parent1宣告為參考或指標時,被
compiler 合成出來的 copy constructor 會把隱含的 vptr 指向 childclass 的 virtual table, 而非
bitwise copy.這個也叫做 upcasting.
第四種情況則請回想一下 virtual base classes, 因為只會有一個實體存在,因此,compiler 會特別審慎考量,不會 bitwise
copy.
NRV最佳化,如下情況會被 compiler
x bar() { X xx; //.... return xx; }
轉為如下的情況
void bar(X& __result) { __result.X::X(); //.... return; }
NRV
NRV -> Named Return Value 提供重要效率改善.
使用 member initialization list.
時機:
- 當初始化一個 reference member
- 當初始化一個 const member 時
- 當喚起一個 base class 的 constructor, 而他擁有一個參數時.
- 當喚起一個 member class 的 constructor, 而他擁有一個參數時.
Why:
一般這樣寫, compiler 會先產生一暫存物件, 再 assign 給 member object, 所以效率不彰.
class Word { String _name; int _cnt; public: Word(){ _name=0; _cnt=0; } };
所以要用 Word::Word:_name(0) {_cnt=0;}; 效率較佳.
缺點及注意事項:
注意 initialization list 的初始順序. 若 member object 依賴性太高,最好不要放到 initialization list
中,而應移至 constructor 中.