初始化 (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 double
到double
或float
,從double
到float
的轉換,除非源值為常量表示式,且其在目標型別中可表示(包括可表示但無法精確表示的情況) - 從整形或 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
T1
與 cv2T2
是 reference related ,表示T1
與T2
相似(約等於相同),或T1
是T2
的基類。 - 型別 cv1
T1
與 cv2T2
是 reference compitable 表示指向 cv2T2
的指標可以透過一個標準轉換序列(standard conversion sequence)轉換為指向 cv1T1
的指標。(約等於 cv1 > cv2,並且T1
與T2
相同,或者是T2
的(可訪問的且無歧義的)基類)
使用 cv2 T2
的值初始化 cv1 T1
的引用規則如下:
目標是左值引用並且滿足以下條件之一:
- initialize 為左值,cv1
T1
與 cv2T2
是 reference compitable,引用繫結到該值(或其基類子物件) T2
是類,但T1
與T2
並非 reference related,但T2
可以轉換為一個 cv3T3
的左值,且 cv1T1
與 cv3T3
是 reference compitable 的,引用繫結到轉換後的T3
的左值(或其基類子物件)
- initialize 為左值,cv1
- 否則,如果目標為非常量左值引用,或者 volatile 左值引用,程式非法
否則:(以下為繫結至右值,prvalue 會被轉換為 glvalue)
- 如果初始化值為一個右值(非位域),或一個函式,且 cv1
T1
與 cv2T2
是 reference compitable,引用繫結到該值(或其基類子物件) T2
是類,但T1
與T2
並非 reference related,但T2
可以轉換為一個 cv3T3
的右值(或函式),且 cv1T1
與 cv3T3
是 reference compitable 的,引用繫結到轉換後的T3
的 glvalue(或其基類子物件)
- 如果初始化值為一個右值(非位域),或一個函式,且 cv1
否則:
- 如果
T1
或T2
是類,且T1
與T2
並非 reference related,則嘗試透過自定義轉換使用初始化值初始化一個 cv1T1
的物件(如不能初始化相應的物件,則程式非法)。轉換的結果將用於初始化目標引用。 其他情況,初始化值將被隱式轉換為 cv1
T1
。引用繫結值轉換的結果。- 如果
T1
與T2
是 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
- 如果