1. 程式人生 > 其它 >C++學習筆記,初始化列表與建構函式

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強迫編譯器生成隱式宣告的移動賦值運算子。