C++學習筆記,初始化列表與建構函式
一,初始化列表
在開始執行組成建構函式體的複合語句之前,所有的直接基類,虛基類,及非靜態資料成員的初始化均已結束。成員初始化列表是能指定這些物件的非預設初始化之處,對於不能預設初始化的基類或非靜態資料成員,例如引用和const限定的型別的成員,必須指定成員初始化器,對沒有成員初始化器的匿名聯合體或變體成員不進行初始化
二.委託建構函式
若類自身的名字在初始化器列表中作為類或識別符號出現,則該列表必須僅由這一個成員初始化器組成;這種建構函式被稱為委託建構函式(delegating constructor),而建構函式列表的僅有成員所選擇的建構函式是目標建構函式
此情況下,首先由過載決議選擇目標建構函式並予以執行,然後控制返回到委託建構函式並執行其函式體。委託建構函式不能遞迴。
1 class Foo { 2 public: 3 Foo(char x, int y) {} 4 Foo(int y) : Foo('a', y) {} // Foo(int) 委託到 Foo(char,int) 5 };
初始化順序:列表中的成員初始化器的順序是不相關的:
1)若建構函式時最終派生類,則按基類宣告的深度優先、從左到右的遍歷中的出現的順序(從左到右指的是基說明符列表中程現的),初始化各個虛基類
2)然後,以在此類的基類說明符列表中出現的從左到右順序,初始化各個直接基類
3)然後,以類定義中的宣告順序,初始化非靜態成員
4)最後,執行建構函式體。
三.轉換建構函式
不以說明符explicit 宣告且可以單個引數呼叫(C++11 前)的建構函式被稱為轉換建構函式(converting constructor)
與只在直接初始化(包括static_cast顯式轉換)中被考慮的explicit建構函式不同,轉換建構函式還作為使用者定義的轉換序列中的一部分,在複製初始化中受到考慮
通常說法是轉換建構函式指定了一個從其實參型別(若存在)到其類型別的隱式轉換。注意非 explicit 使用者定義轉換函式也指定一個隱式轉換。
隱式宣告的及使用者定義的非 explicit 複製建構函式與移動建構函式也是轉換建構函式。
四,複製建構函式
類名(類名 &)
凡在物件從同類型的另一物件(以直接初始化或複製初始化)初始化時,呼叫複製建構函式。
1)初始化:T a=b或T a(b);b為型別T
2)函式實參傳遞f(a),其中a型別為T而f(T t);
3函式返回:在如T f()這樣函式內部return a;a型別為T,它沒有移動建構函式
隱式宣告的複製建構函式
若不對類提供任何使用者定義的複製建構函式,編譯器始終會宣告一個複製建構函式,作為其類非explicit的inline public 成員。
當以下各項均為真時,這個隱式宣告的複製建構函式擁有形式T::T(constT&):
T
的每個直接與虛基類B
均擁有複製建構函式,其形參為constB&或constvolatileB&;T
的每個類型別或類型別陣列的非靜態資料成員M
均擁有複製建構函式,其形參為constM&或constvolatileM&。
否則,隱式宣告的複製建構函式是T::T(T&)。(注意因為這些規則,隱式宣告的複製建構函式不能繫結到 volatile 左值實參)。
類可以擁有多個複製建構函式,如T::T(constT&)和T::T(T&)。
當存在使用者定義的複製建構函式時,使用者仍可用關鍵詞default
強迫編譯器生成隱式宣告的複製建構函式。(C++11)
1 struct A 2 { 3 int n; 4 A(int n = 1) : n(n) { } 5 A(const A& a) : n(a.n) { } // 使用者定義的複製建構函式 6 }; 7 8 struct B : A 9 { 10 // 隱式預設建構函式 B::B() 11 // 隱式複製建構函式 B::B(const B&) 12 }; 13 14 struct C : B 15 { 16 C() : B() { } 17 private: 18 C(const C&); // 不可複製,C++98 風格 19 }; 20 21 int main() 22 { 23 A a1(7); 24 A a2(a1); // 呼叫複製建構函式 25 B b; 26 B b2 = b; 27 A a3 = b; // 轉換到 A& 並呼叫複製建構函式 28 volatile A va(10); 29 // A a4 = va; // 編譯錯誤 30 31 C c; 32 // C c2 = c; // 編譯錯誤 33 }
五,移動建構函式
類名(類名 &&)
當(直接初始化或複製初始化)從同類型的右值初始化物件時,呼叫移動構造建構函式,包含
1)初始化T a =std::move(b);或T a(std::move(b)),b型別為T
2)函式實參傳遞f(std::move(a));其中a型別為T而f為Ret f(T f);
3)函式返回:在T f()的函式中 return a;其中啊型別為T,
隱式宣告的移動建構函式
若不對類型別(struct、class或union)提供任何使用者定義的移動建構函式,且下列各項均為真:
- 沒有使用者宣告的複製建構函式;
- 沒有使用者宣告的複製賦值運算子;
- 沒有使用者宣告的移動複製運算子;
- 沒有使用者宣告的解構函式;
則編譯器將宣告一個移動建構函式,作為其類的非 explicit的inline public
成員,簽名為T::T(T&&)
。
類可以擁有多個移動建構函式,例如T::T(constT&&)和T::T(T&&)。當存在使用者定義的移動建構函式時,使用者仍可用關鍵詞default
強制編譯器生成隱式宣告的移動建構函式。
1 #include <string> 2 #include <iostream> 3 #include <iomanip> 4 #include <utility> 5 6 struct A 7 { 8 std::string s; 9 int k; 10 A() : s("test"), k(-1) { } 11 A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; } 12 A(A&& o) noexcept : 13 s(std::move(o.s)), // 類型別成員的顯式移動 14 k(std::exchange(o.k, 0)) // 非類型別成員的顯式移動 15 { } 16 }; 17 18 A f(A a) 19 { 20 return a; 21 } 22 23 struct B : A 24 { 25 std::string s2; 26 int n; 27 // 隱式移動建構函式 B::(B&&) 28 // 呼叫 A 的移動建構函式 29 // 呼叫 s2 的移動建構函式 30 // 並進行 n 的逐位複製 31 }; 32 33 struct C : B 34 { 35 ~C() { } // 解構函式阻止隱式移動建構函式 C::(C&&) 36 }; 37 38 struct D : B 39 { 40 D() { } 41 ~D() { } // 解構函式阻止隱式移動建構函式 D::(D&&) 42 D(D&&) = default; // 強制生成移動建構函式 43 }; 44 45 int main() 46 { 47 std::cout << "Trying to move A\n"; 48 A a1 = f(A()); // 按值返回時,從函式形參移動構造其目標 49 std::cout << "Before move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; 50 A a2 = std::move(a1); // 從亡值移動構造 51 std::cout << "After move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; 52 53 std::cout << "Trying to move B\n"; 54 B b1; 55 std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n"; 56 B b2 = std::move(b1); // 呼叫隱式移動建構函式 57 std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n"; 58 59 std::cout << "Trying to move C\n"; 60 C c1; 61 C c2 = std::move(c1); // 呼叫複製建構函式 62 63 std::cout << "Trying to move D\n"; 64 D d1; 65 D d2 = std::move(d1); 66 }
六,解構函式
是物件生存期終結時呼叫的特殊成員函式,目的時釋放物件可能在其生成期間獲得的資源
~類名();virtual ~類名()虛解構函式在基類中常為必要的
生存期結束包含:1)程式終止,對於靜態儲存的物件;2)退出執行緒,對於具有執行緒區域性儲存的物件
3)作用域結束,對於具有自動儲存期的物件和生存期因繫結到引用而延長的臨時量;
4)delete 表示式,對於具有動態儲存的物件5)完整表示式的結尾,對於無名臨時量;
6)棧回朔,對於具有自動儲存期的物件,當未捕捉的異常脫離其塊時;
虛解構函式:通過指向基類指標刪除物件引發未定義行為,除非基類的解構函式為虛擬函式
1 class Base { 2 public: 3 virtual ~Base() {} 4 }; 5 class Derived : public Base {}; 6 Base* b = new Derived; 7 delete b; // 安全
純虛構函式:解構函式可以宣告為純虛,例如對於需要宣告為抽象類,但沒有其他可宣告為純虛的適合函式的基類。這種解構函式必須有定義,因為在銷燬派生類時,所有基類解構函式都總是得到呼叫:
1 class AbstractBase { 2 public: 3 virtual ~AbstractBase() = 0; 4 }; 5 AbstractBase::~AbstractBase() {} 6 class Derived : public AbstractBase {}; 7 // AbstractBase obj; // 編譯錯誤 8 Derived obj; // OK
七,複製賦值運算子
類名&
類名::
operator=
(類名)
若不對類型別(struct、class或union)提供任何使用者定義的複製賦值運算子,則編譯器將始終宣告一個,作為類的 inline public 成員。當以下各項均為真時,這個隱式宣告的複製賦值運算子擁有形式T&T::operator=(constT&):
T
的每個直接基類B
均擁有複製賦值運算子,其形參是B或constB&或constvolatileB&;T
的每個類型別或類陣列型別的非靜態資料成員M
均擁有複製賦值運算子,其形參是M或constM&或constvolatileM&。
否則隱式宣告的複製賦值運算子被宣告為T&T::operator=(T&)。(注意因為這些規則,隱式宣告的複製賦值運算子不能繫結到 volatile 左值實參。)
八.移動賦值運算子
類名&
類名:: operator=
(類名&&
)
若不對類型別(struct、class或union)提供任何使用者定義的移動賦值運算子,且下列各項均為真:
- 沒有使用者宣告的複製建構函式;
- 沒有使用者宣告的移動建構函式;
- 沒有使用者宣告的複製賦值運算子;
- 沒有使用者宣告的解構函式,
則編譯器將宣告一個移動賦值運算子,作為其類的inline public
成員,並擁有簽名T& T::operator=(T&&)
。
類可以擁有多個移動賦值運算子,如T&T::operator=(constT&&)和T&T::operator=(T&&)。當存在使用者定義的移動賦值運算子時,使用者仍可用關鍵詞default
強迫編譯器生成隱式宣告的移動賦值運算子。