概念很簡單,以下的constructor寫得不好:
class Person { String _name; int _age; public: Person() { _name = 0; _age = 0; } };因為compiler會把它轉化成以下(*):
Person() { String tmpString(0); // temporary String object been created String _name.String(); _name = tmpString; // copy assignment operator been called tmpString.~String(); // destruct temporary String object _age = 0; // basic data type, set value directly }
可以看到,一個暫時的String物件被建立,再複製給實際的data memer(_name),然後再呼叫這個暫時物件的destructor(**)。
可以想像如果Person class被大量create,有不少時間會花在暫時物件上。比較好的寫法是用initialization list:
Person() : _name(0) { _age = 0; }這樣寫比較好是因為compiler會把它轉化成:
Person() { _name.String(0); // only one String constructor called _age = 0; }
知道了以上事實後,可以破除一項迷思: 什麼東西都要放到initialization list? 你可能看過以下的code:
class X { int a; bool b; char c; double d; public: X() : a(0), b(false), c(0), d(0.0) {} };這樣的寫法除了看起來比較有學問一點之外,也只是讓compiler辛苦一點,要幫我們再轉成原本就應該寫的樣子:
X() { a = 0; b = false; c = 0; d = 0.0; }
接下來,使用initialization list還有個要注意的地方,初始化的順序。例如下面的class初始化的結果會是錯的:
class Y { bool _AllowToDrink; int _age; public: Y(int age) : _age(age), _AllowToDrink(_age >= 18 ? true : false) {} };因為data member初始化的順序是以宣告的順序為主,而不是以initialization list中的順序。所以這個例子中的_AllowToDrink的值會先被evaluate,但此時_age還沒給初值,所以_AllowToDrink的結果是未知的。
這個例子的問題解法有二,一個是改變data member宣告的順序,先宣告_age再宣告_AllowToDrink。不過比較好的解法是把_AllowToDrink的初始動作放到constructor的本體中:
Y(int age) : _age(age) { _AllowToDrink = _age >= 18 ? true : false; }這個解法利用compiler會把initialization list中的data member轉化到constructor本體的最前面這個原則:
Y(int age) { _age = age; // compiler produced code, guarantee to be placed before user code. _AllowToDrink = _age >= 18 ? true : false; }如此,無論_age與_AllowToDrink的宣告順序為何,都可以正確被賦予初值了。
* 忽略this指標等轉化以強調今天的主題
** 為什麼Compiler要做這樣轉化呢? 雖然programmer想的是要把_name物件做初始化,但寫出來的code的語意(semantic)卻是要做copy assignment (_name = 0;),而且雖然型別不對(把int硬塞給給String去吃),但compiler還會很雞婆的幫我們把整數0做轉型成String (如果String有int為參數的constructor的話,否則會出compile time error)。為了消除這樣的誤會,因此C++才會訂出constructor initialization list這樣的語法。
太有心了~
回覆刪除呵 我只是看書後想順便作筆記XD
回覆刪除thx to share very useful!
回覆刪除