標籤:style blog color 使用 資料 問題 div amp
看這樣一個模板,它產生的類使得一個名字和一個t類型的對象的指標關聯起來。
template<class t>class namedptr {public: namedptr(const string& initname, t *initptr); ...private: string name; t *ptr;};
在寫namedptr建構函式時,必須將參數值傳給相應的資料成員。有兩種方法來實現。第一種方法是使用成員初始化列表:
template<class t>namedptr<t>::namedptr(const string& initname, t *initptr ): name(initname), ptr(initptr){}
第二種方法是在建構函式體內賦值:
template<class t>namedptr<t>::namedptr(const string& initname, t *initptr){ name = initname; ptr = initptr;}
從純實際應用的角度來看,有些情況下必須用初始化。特別是const和引用資料成員只能用初始化,不能被賦值。所以,如果想讓namedptr<t>對象不能改變它的名字或指標成員,就必須遵循條款21的建議聲明成員為const:
template<class t>class namedptr {public: namedptr(const string& initname, t *initptr); ...private: const string name; t * const ptr;};
這個類的定義要求使用一個成員初始化列表,因為const成員只能被初始化,不能被賦值。
如果namedptr<t>對象包含一個現有名字的引用,情況會非常不同。但還是要在建構函式的初始化列表裡對引用進行初始化。還可以對名字同時聲明const和引用,這樣就產生了一個其名字成員在類外可以被修改而在內部是唯讀對象。
template<class t>class namedptr {public: namedptr(const string& initname, t *initptr); ...private: const string& name; // 必須通過成員初始化列表 // 進行初始化 t * const ptr; // 必須通過成員初始化列表 // 進行初始化};
然而前面最初的類模板不包含const和引用成員。即使這樣,用成員初始化列表還是比在建構函式裡賦值要好。這次的原因在於效率。當使用成員初始化列表時,只有一個string成員函數被調用。而在建構函式裡賦值時,將有兩個被調用。為了理解為什麼,請看在聲明namedptr<t>對象時都發生了些什麼。
對象的建立分兩步:
1. 資料成員初始化。(參見條款13)
2. 執行被調用建構函式體內的動作。
(對有基類的對象來說,基類的成員初始化和建構函式體的執行發生在衍生類別的成員初始化和建構函式體的執行之前)
對namedptr類來說,這意味著string對象name的建構函式總是在程式執行到namedptr的建構函式體之前就已經被調用了。問題只在於:string的哪個建構函式會被調用?
這取決於namedptr類的成員初始化列表。如果沒有為name指定初始化參數,string的預設建構函式會被調用(隱式初始化)。當在namedptr的建構函式裡對name執行賦值時,會對name調用operator=函數。這樣總共有兩次對string的成員函數的調用:一次是預設建構函式,另一次是賦值。
相反,如果用一個成員初始化列表來指定name必須用initname來初始化(顯式初始化),name就會通過拷貝建構函式以僅一個函數調用的代價被初始化。
請注意static類成員永遠也不會在類的建構函式初始化。靜態成員在程式啟動並執行過程中只被初始化一次,所以每當類的對象建立時都去“初始化”它們沒有任何意義(static成員不屬於類對象)。至少這會影響效率:既然是“初始化”,那為什麼要去做多次?而且,靜態類成員的初始化和非靜態類成員有很大的不同,這專門有一個條款m47來說明。