1 class MyCppClass 2 { 3 }
一旦我們為一個類編寫了default constructor,那麼編譯器也就不會為其默認生成default constructor,對於其他幾個函數也一樣。對於編譯器默認生成的constructor來說,它會以一定規則對每一個數據成員進行初始化。考慮到成員初始化的重要性,在編寫自己的constructor時就需要嚴謹認真了,特別是在類的派生與繼承情況下這點顯得尤為重要。對於copy constructor和assignment operator的運用場景,這裡不得不多說一點,見如下代碼:
1 #include <iostream> 2 3 using std::cout; 4 using std::endl; 5 6 class MyCppClass 7 { 8 public: 9 MyCppClass() 10 { 11 std::cout <<"In Default Constructor!" <<std::endl; 12 } 13 14 MyCppClass(const MyCppClass& rhs) 15 { 16 std::cout <<"In Copy Constructor!" <<std::endl; 17 } 18 19 MyCppClass& operator= (const MyCppClass& rhs) 20 { 21 std::cout <<"In Copy Assignment Operator!" <<std::endl; 22 23 return *this; 24 } 25 }; 26 27 int main() 28 { 29 MyCppClass testClass1; // default constructor 30 MyCppClass testClass2(testClass1); // copy constructor 31 testClass1 = testClass2; // copy assignment operator 32 33 MyCppClass testClass3 = testClass1; // copy constructor 34 35 return 0; 36 }
執行結果:
1 // 數據成員類型為內置類型 2 class MyCppClass 3 { 4 public: 5 // 賦值操作進行成員初始化 6 MyCppClass 7 { 8 counter = 0; 9 } 10 11 // 初始化列表進行成員初始化 12 MyCppClass : counter(0) 13 { 14 } 15 16 private: 17 int counter; 18 }
當類的數據成員類型為內置類型時,上面兩種初始化方式的效果一樣。當數據成員的類型同樣也為一個類時,初始化的過程就會有不一樣的地方了,比如:
1 // 數據成員類型為自定義類型:一個類 2 class MyCppClass 3 { 4 public: 5 // 賦值操作進行成員初始化 6 MyCppClass(string name) 7 { 8 counter = 0; 9 theName = name; 10 } 11 12 // 初始化列表進行成員初始化 13 MyCppClass : counter(0), theName(name) 14 { 15 } 16 17 private: 18 int counter; 19 string theName; 20 }
在構造函數體內的theName = name這條語句,theName先會調用string的default constructor進行初始化,之後再調用copy assignment opertor進行拷貝賦值。而對於初始化列表來說,直接通過copy constructor進行初始化。明顯起見,可以通過如下的代碼進行測試。
1 2 #include <iostream> 3 #include <string> 4 5 class SubClass 6 { 7 public: 8 SubClass() 9 { 10 std::cout <<" In SubClass Default Constructor!" <<std::endl; 11 } 12 13 SubClass(const SubClass& rhs) 14 { 15 std::cout <<" In SubClass Copy Constructor!" <<std::endl; 16 } 17 18 SubClass& operator= (const SubClass& rhs) 19 { 20 std::cout <<" In SubClass Copy Assignment Operator!" <<std::endl; 21 22 return *this; 23 } 24 }; 25 26 class BaseClass 27 { 28 public: 29 BaseClass(const SubClass &rhs) 30 { 31 counter = 0; 32 theBrother = rhs; 33 std::cout <<" In BaseClass Default Constructor!" <<std::endl; 34 } 35 36 BaseClass(const SubClass &rhs, int cnt):theBrother(rhs),counter(cnt) 37 { 38 std::cout <<" In BaseClass Default Constructor!" <<std::endl; 39 } 40 41 BaseClass(const BaseClass& rhs) 42 { 43 std::cout <<" In BaseClass Copy Constructor!" <<std::endl; 44 } 45 46 BaseClass& operator= (const BaseClass& rhs) 47 { 48 std::cout <<" In BaseClass Copy Assignment Operator!" <<std::endl; 49 50 return *this; 51 } 52 private: 53 int counter; 54 SubClass theBrother; 55 }; 56 57 int main() 58 { 59 SubClass subClass; 60 61 std::cout <<"\nNo Member Initialization List: " <<std::endl; 62 BaseClass BaseClass1(SubClass); 63 64 std::cout <<"\nMember Initialization List: " <<std::endl; 65 BaseClass BaseClass2(SubClass, 1); 66 67 return 0; 68 }
執行結果:
也就是,在涉及到自定義類型初始化的時候,使用初始化列表來完成初始化在效率上會有著更佳的表現。這也是初始化列表的一大閃光點。即便對於內置類型,在一些情況下也是需要使用初始化列表來完成初始化工作的,比如const、references成員變量。這裡有篇筆記,對初始化列表有著非常詳盡的描述。 幾個初始化名詞 在閱讀《Accelerated C++》中文版時,總是碰到“缺省初始化”、“隱式初始化”以及“數值初始化”,最初在理解這幾個名詞的時候幾費周折,總覺得為什麼一個初始化操作造出了如此多的名詞,為此沒少花時間來弄清楚它們之間的關系。1 int x = 0; // 顯示初始化x 2 SubClass subClass; // 依賴SubClass的default constructor進行初始化
上面的名詞“缺省初始化”描述的就是當內置類型或者自定義類型的數據沒有進行顯示初始化時的一種初始化狀態,而“隱式初始化”描述的是在該狀態下面進行的具體操作方式,比如對於內置類型來說,缺省初始化狀態下進行的隱式初始化實際上是未定義的,而自定義類型的隱式初始化則依賴於其constructor。
前面提到過C++不保證內置類型的初始化,但是當內置類型在作為一個類的成員時,在某些特定的條件下該內置類型的成員會被編譯器主動進行初始化,對於這個過程也就是所謂的數值初始化。在《Accelerated C++》當中列出了如下的幾種情況: 測試如下:1 #include <iostream> 2 #include <vector> 3 #include <map> 4 #include <string> 5 6 using std::cout; 7 using std::endl; 8 using std::vector; 9 using std::map; 10 using std::string; 11 12 class NumbericInitTestClass 13 { 14 public: 15 void PrintCounter() 16 { 17 cout <<"counter = " <<counter <<endl; 18 } 19 private: 20 int counter; 21 }; 22 23 24 int main() 25 { 26 NumbericInitTestClass tnc; 27 tnc.PrintCounter(); 28 29 map<string, int> mapTest; 30 cout <<mapTest["me"] <<endl; 31 32 vector<NumbericInitTestClass> vecNumbericTestClass(1); 33 vecNumbericTestClass[0].PrintCounter(); 34 35 return 0; 36 }
對於沒有進行初始化的內置類型,是一個未定義的值2009095316,而對於2, 3種情況來說,均被初始化為0,對於第1種情況我還沒有想到合適的場景。 回過頭想想,為了書中的一些相似的名詞,去想辦法把它們湊在一起總是顯得有些牽強附會:) 一些規則 這裡附上幾條有關初始化的基本規則,它們多來源於《Effective C++》: 1. 為內置型對象進行手工初始化,因為C++不保證初始化它們。 2. 構造函數最好使用成員初值列(member initialization list),而不要在構造函數體內使用賦值操作。初值列列出的成員變量,其排列次序應該和它們在class中聲明的次序相同。 3. C++不喜歡析構函數吐出異常。 4. 在構造函數與析構函數期間不要調用virtual函數,因為這類調用從不下降至derived class。 5. copying函數應該確保復制“對象內所有成員變量”及“所有base class成分”。