初始化 (Initialization)

fefe發表於2022-03-13

初始化 (Initialization)

  • 初始化的形式

    • Direct-initialization
    • Copy-initialization
  • 初始化的含義
  • Value initialize
  • Default initialize
  • Zero initialize
  • List initialize

    • narrowing conversion
  • 字元陣列初始化
  • 聚合(aggregate)初始化

    • Aggregate
  • 引用初始化

基於c++20 draft n4868

初始化的形式

通常講到初始化,會想起變數的時候給的初始化值(initializer)。但是除此之後,還有一些地方會發生物件的初始化。所有的這些初始化操作遵從同一套規則。

初始化按照其形式,可以分為兩大類,direct-initialization與copy-initialization

Direct-initialization

  • 變數定義中初始化值無=

    • int i{1}; std::vector<std::string> vec(3);
  • new-initializer

    • int *p = new int(3);,使用3初始化一個新建的int物件
  • static_cast

    • static_cast 會使用其引數初始化一個結果型別的物件,如:static_cast<int>(2.0);,其中使用2.0初始化一個int物件。
  • 函式呼叫像是的型別轉換

    • int(2.0),其中使用2.0初始化一個int型別的物件
  • condition 中的 brace-init-list

    • if (Derived *pDerived{dynamic_cast<Derived*>(pBase)}),使用 dyanmic_cast<Drived*>(pBase) 初始化 pDerived

Copy-initialization

  • 變數定義中使用 = 形式初始化

    • int i = 1; ,使用 1 初始化 i
  • condition 中使用 = 形式初始化

    • if (Derived *pDerived = dynamic_cast<Derived*>(pBase)),使用 dyanmic_cast<Drived*>(pBase) 初始化 pDerived
  • 引數傳遞,函式返回值

    • 示例:

      int foo(int i) {
        return i+1;
      }
      void bar() {
        foo(1);
      }

      使用 1 初始化 foo 的引數 i,使用 i+1 初始化函式的返回值(右值)

  • 丟擲異常,異常捕獲

    • 示例:

      void foo() {
        std::string s{123};
        try {
          throw s;
        } cache (string &es) {
        }
      }

      throw s; 中,使用 s 初始化一個臨時物件。在 cache (string &es) 中,臨時物件的左值用於初始化 es。這兩個初始化都是 copy initialize

  • 聚合成員初始化。在聚合初始化中,每個成員的初始化都是 copy initialization

    • std::string str[]{"abc", "def"},使用 "abc" 初始化 str[0] ,使用 "def" 初始化 str[1] 都是 copy-initialize 。注意對 str 的初始化使用的是 direct-initialize 的形式。

初始化的含義

根據被初始化物件的型別與初始化的形式不同,初始化會有不同的含義。具體初始化含義見下。copy-initialize 與 direct-initialize 均使用如下定義。

  • 如果 initializer 是一個 braced-init-list 或者 = branced-init-list ,執行列表初始化
  • 如果目標型別是引用型別,則執行引用初始化
  • 如果目標型別是 char, singed char, unsigned char, char8_t, char16_t, char32_t, wchar_t 的陣列,initializer 是一個 string-literal ,執行字元陣列初始化
  • 如果 initializer 是 () ,執行 value-initialze
  • 如果目標型別是陣列,initializer 應該是 ( pxpression-list ),且其中的元素個數不應比陣列長度更多。陣列中的元素將依次由 epression-list 中的值 copy-initialize。對沒有提供值的元素(陣列長度比 expression-list 長),進行 value-initialize
  • 如果目標型別是類型別(class type):

    • 如果 initializer 是一個右值並且與目標型別相同(忽略 cv-qualifier),則 initializer 用於初始化目標物件
    • 如果 1) 這是 direct initialization ;或者 2) 這是 copy initialization 且源型別與目標型別相同,或者是目標型別的派生類,那麼使用建構函式初始化

      • 如果可以找到唯一可用的建構函式,那麼該建構函式用於初始化物件
      • 如果沒有可用的建構函式,且目標型別是一個聚合(aggregate class),intializer 為 ( expression-list ) ,則使用 expression-list 中的值依次 copy initialize對應元素。未初始化值的元素執行 value initialize。
      • 否則,程式非法
    • 否則,嘗試使用者定義型別轉換(包括建構函式)。不能找到合適的型別轉換的程式非法。型別轉換的結果將被用於 direct initialize 目標物件。
  • 否則,如果源型別是一個類型別,將嘗試使用者定義型別轉換。不能找到合適的型別轉換的程式非法。
  • 否則,如果這個是一個 direct initialization ,原型別是 std::nullptr_t,目標型別是 bool ,則目標物件被初始化為 false
  • 否則, 如果需要,可以使用一個標準轉換序列(stand conversion sequence)將源物件轉換為目標型別。無法轉換則程式非法。

Value initialize

對一個型別 value initialize 含義是:

  • 如果其為類型別,則

    • 如果該類沒有預設建構函式,或者有使用者提供的(user-provided)或 deleted 預設建構函式,該物件被 default initialize
    • 否則,物件被 zero initialize;並且,如果該類有 non-trivial 預設建構函式,該物件被 value initialize。

      • 例如

        #include <string>
        struct A{
          int a;
          std::string s;
        };
        int main()
        {
          A a_obj{}; // a_obj.a == 0
        }

        A 有預設建構函式(自動生成的),該預設建構函式不是 user-provided ,也不是 deleted,因而進入該條處理。
        a_obj 被 zero initialize 。另外,由於其預設建構函式是 non-trivial (它含有個一個 std::string 成員),因而被進一步預設構造。
        由於有 zero initialize 存在,a_obj.a == 0

  • 如果目標物件型別為陣列,則陣列的每一個元素被 value initialize
  • 否則,物件被 zero initialize

Default initialize

對一個型別 default initialize 含義是:

  • 對類型別,呼叫預設建構函式。
  • 對陣列型別,每一個元素被 default initialize
  • 其他,不執行初始化操作

Zero initialize

對一個型別 default initialize 含義是:

  • scalar type 初始化為 0
  • 對非 union 類型別,其 padding 被初始化為 0,非靜態資料成員,基類 被 zero initialize
  • 對 union ,padding 被初始化為 0 ,第一個非靜態成員被 zero initialize
  • 對陣列,每一個元素被 zero initialize
  • 對引用,不進行初始化

List initialize

  • 如果 brace-init-list 包含 designated-initializer-list ,目標型別必須為聚合(aggregate class),其中成員的出現順序需要與定義順序一致。執行聚合初始化(aggregate initialize)
  • 如果目標是聚合類(aggregate class),initializer list 僅包含一個元素,其型別與目標型別相同或者是其派生類,那麼使用該元素初始化目標物件。
  • 如果目標型別是陣列,initializer list 僅包含一個元素,且為合適型別的字串字面量,則執行字元陣列初始化。
  • 如果目標型別是聚合型別(aggregate class),執行聚合初始化
  • 如果initializer list 為空,目標型別為有預設建構函式的類型別,目標物件被 value initialize
  • 如果目標型別是 std::initializer_list<E> :建立一個 const E[N] 型別的陣列,其中每一個元素用 initializer list 中的元素 copy initialize ,目標物件將引用這個陣列。(如果任何一個元素初始化是需要 narrowing conversion,則程式非法)
  • 如果目標型別是類型別,使用建構函式初始化。(如果引數初始化時需要 narrowing conversion,程式非法)
  • 如果目標型別是有確定 underlying type 的列舉,intiiailizer list 僅含有一個元素,該元素可以被隱式轉換為目標型別的 underlying type ,並且該初始化是 direct initialization,則目標物件的值為源物件轉換為目標型別的 underlying type 後的值。(不可以是 copy initialize;如果需要 narrawing conversion ,程式非法)
  • 如果 initialize list 有唯一元素,目標型別不為引用或者為一個 reference related to 源型別的引用,目標物件(或引用)由該元素初始化(如果需要 narrowing conversion,程式非法)
  • 如果目標型別是引用,則用 initializer 執行 copy list initialize 初始化一個被引用型別的 prvalue,目標引用由該 prvalue direct initialize
  • 如果 initializer list 為空,目標物件被 value initialize
  • 其他,程式非法

narrowing conversion

narrowing conversion 包括:

  • 從浮點型別到整形的轉換
  • long doubledoublefloat,從 doublefloat 的轉換,除非源值為常量表示式,且其在目標型別中可表示(包括可表示但無法精確表示的情況)
  • 從整形或 unscoped enueration type 到浮點型別的轉換,除非源值是一個常量表示式,且轉換結果在目標型別可表示,且將轉換結果再轉換為源型別時其值不變
  • 從整形或 unscoped enueration type 到整型的轉換,該整型無法表示源型別中的所有值,除非源值是一個常量表示式,其值在經過 integral promotion 之後在目標型別可表示
  • 從指標或指向成員的指標型別到 bool

字元陣列初始化

字元陣列可以用字串字面量初始化。字元陣列的型別需要與字串字面量型別匹配。字元陣列的中的元素由字串字面量中的對應元素進行初始化。

字串字面量(包括最後的 '\0')不能比字元陣列更長。如果字串字面量較短,多餘的陣列元素將被 zero initialize 。

聚合(aggregate)初始化

Aggregate

滿足以下條件的陣列或類(union 也是一種類)稱作 aggregate:

  • 沒有宣告建構函式,或繼承(注:使用 using)建構函式
  • 沒有私有或保護的非靜態資料成員
  • 沒有虛擬函式
  • 沒有虛的、私有的或被保護的基類

aggregate 的成員指:

  • 對於陣列,指它的每一個元素
  • 對於類,指按宣告順序的每一個直接基類,以及按宣告順序的每一個非靜態資料成員(如果類中有匿名聯合(union),匿名聯合本身是 aggregate 的一個成員,但匿名聯合的成員不是)

對於顯式提供了值的成員,將使用該值進行 copy initialize。如果需要 narrowing conversion,程式非法。對於匿名聯合(anonymous union),只能對其中一個成員提供初始化。

對於非 union 的 aggregate 中沒有被顯式初始化的成員:

  • 如果成員宣告瞭 default member initializer,使用該值初始化
  • 如果該成員不是引用,使用 {} copy initialize
  • 否則,程式非法

當一個 union aggregate 被使用 {} 初始化時:

  • 如果任意成員(variant member)有 default member initializer,使用該值初始化這一成員
  • 否則,union 的第一個成員使用 {} 初始化

aggregate 的成員如果也是 aggregate ,子 aggregate 的初始化列表(在某些情況下)可以省略 {, }。空 {} 不能省略。如果省略,那麼接下來的 initializer 全部被用於初始化子 aggregate ,直到子 aggregate 所有成員都有了 initializer 。剩餘的 initializer 用於初始化父 aggregate 的剩餘成員。

引用初始化

先要介紹兩個概念:

  • 型別 cv1 T1cv2 T2 是 reference related ,表示 T1T2 相似(約等於相同),或 T1T2 的基類。
  • 型別 cv1 T1cv2 T2 是 reference compitable 表示指向 cv2 T2 的指標可以透過一個標準轉換序列(standard conversion sequence)轉換為指向 cv1 T1 的指標。(約等於 cv1 > cv2,並且 T1T2 相同,或者是 T2 的(可訪問的且無歧義的)基類)

使用 cv2 T2 的值初始化 cv1 T1 的引用規則如下:

  • 目標是左值引用並且滿足以下條件之一:

    • initialize 為左值,cv1 T1cv2 T2 是 reference compitable,引用繫結到該值(或其基類子物件)
    • T2 是類,但 T1T2 並非 reference related,但 T2 可以轉換為一個 cv3 T3 的左值,且 cv1 T1cv3 T3 是 reference compitable 的,引用繫結到轉換後的 T3 的左值(或其基類子物件)
  • 否則,如果目標為非常量左值引用,或者 volatile 左值引用,程式非法
  • 否則:(以下為繫結至右值,prvalue 會被轉換為 glvalue)

    • 如果初始化值為一個右值(非位域),或一個函式,且 cv1 T1cv2 T2 是 reference compitable,引用繫結到該值(或其基類子物件)
    • T2 是類,但 T1T2 並非 reference related,但 T2 可以轉換為一個 cv3 T3 的右值(或函式),且 cv1 T1cv3 T3 是 reference compitable 的,引用繫結到轉換後的 T3 的 glvalue(或其基類子物件)
  • 否則:

    • 如果 T1T2 是類,且 T1T2 並非 reference related,則嘗試透過自定義轉換使用初始化值初始化一個 cv1 T1 的物件(如不能初始化相應的物件,則程式非法)。轉換的結果將用於初始化目標引用。
    • 其他情況,初始化值將被隱式轉換為 cv1 T1。引用繫結值轉換的結果。

      • 如果 T1T2 是 reference related,那麼 cv1 應大於 cv2 ,不能用 lvalue 初始化 rvalue reference 。

    例如:

    struct Banana { };
    struct Enigma { operator const Banana(); };
    struct Alaska { operator Banana&(); };
    void enigmatic() {
      typedef const Banana ConstBanana;
      Banana &&banana1 = ConstBanana(); // error
      Banana &&banana2 = Enigma(); // error
      Banana &&banana3 = Alaska(); // error
    }
    const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0
    double&& rrd = 2; // rrd refers to temporary with value 2.0
    const volatile int cvi = 1;
    const int& r2 = cvi; // error: cv-qualifier dropped
    struct A { operator volatile int&(); } a;
    const int& r3 = a; // error: cv-qualifier dropped
    // from result of conversion function
    double d2 = 1.0;
    double&& rrd2 = d2; // error: initializer is lvalue of related type
    struct X { operator int&(); };
    int&& rri2 = X(); // error: result of conversion function is lvalue of related type
    int i3 = 2;
    double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0

相關文章