概念很簡單,以下的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!
回覆刪除