名稱空間
變體
操作

列表初始化 (C++11 起)

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

使用花括號初始值設定項列表初始化物件。

目錄

[編輯] 語法

[編輯] 直接列表初始化

T object { arg1, arg2, ... };

T object{.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(1)
T { arg1, arg2, ... }

T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(2)
new T { arg1, arg2, ... }

new T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(3)
Class { T member { arg1, arg2, ... }; };

Class { T member {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(4)
Class::Class() : member { arg1, arg2, ... } {...

Class::Class() : member {.des1 = arg1 , .des2 { arg2 } ...} {...

(C++20 起)
(5)

[編輯] 複製列表初始化

T object = { arg1, arg2, ... };

T object = {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(6)
function ({ arg1, arg2, ... })

function ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(7)
return { arg1, arg2, ... };

return {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(8)
object [{ arg1, arg2, ... }]

object [{.des1 = arg1 , .des2 { arg2 } ... }]

(C++20 起)
(9)
object = { arg1, arg2, ... }

object = {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(10)
U ({ arg1, arg2, ... })

U ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(11)
Class { T member = { arg1, arg2, ... }; };

Class { T member = {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(12)

列表初始化在以下情況發生

  • 直接列表初始化(顯式和非顯式建構函式都考慮)
1) 用花括號初始值設定項列表初始化具名變數
2) 用花括號初始值設定項列表初始化無名臨時物件
3)new 表示式初始化具有動態儲存期的物件,其中初始值設定項是花括號初始值設定項列表
4) 在不使用等號的非靜態資料成員初始值設定項
5) 在建構函式的成員初始值設定項列表中,如果使用花括號初始值設定項列表
  • 複製列表初始化(顯式和非顯式建構函式都考慮,但只能呼叫非顯式建構函式)
6) 在等號後用花括號初始值設定項列表初始化具名變數
7) 在函式呼叫表示式中,使用花括號初始值設定項列表作為引數,並且列表初始化用於初始化函式引數
8)return 語句中,使用花括號初始值設定項列表作為返回表示式,並且列表初始化用於初始化返回物件
9) 在使用使用者定義operator[]下標表達式中,其中列表初始化用於初始化過載運算子的引數
10)賦值表示式中,其中列表初始化用於初始化過載運算子的引數
11) 函式式轉換表示式或其他建構函式呼叫,其中使用花括號初始值設定項列表代替建構函式引數。複製列表初始化初始化建構函式的引數(注意;此示例中的型別U不是正在列表初始化的型別;而是U的建構函式的引數)
12) 在使用等號的非靜態資料成員初始值設定項

[編輯] 解釋

型別為(可能是 cv 限定的)T 的物件的列表初始化的效果是:

  • 如果花括號初始值設定項列表包含指定初始值設定項列表並且T不是引用型別,則T必須是聚合類。指定初始值設定項列表中的指定符中排序的識別符號必須形成T的直接非靜態資料成員中排序的識別符號的子序列。聚合初始化被執行。
(C++20 起)
  • 否則,如果T是聚合類且花括號初始值設定項列表,不包含指定初始值設定項列表,(C++20 起)有一個相同或派生型別(可能帶 cv 限定符)的單個初始值設定項子句,則物件由該初始值設定項子句初始化(對於複製列表初始化透過複製初始化,對於直接列表初始化透過直接初始化)。
  • 否則,如果T是字元陣列,並且花括號初始值設定項列表有一個單一的初始值設定項子句是一個適當型別的字串字面量,則該陣列照常從字串字面量初始化
  • 否則,如果花括號初始值設定項列表為空且T是具有預設建構函式的類型別,則執行值初始化
  • 否則,如果T是類型別,則分兩個階段考慮T的建構函式:
  • 如果前一階段未產生匹配,則T的所有建構函式都參與過載決議,以花括號初始值設定項列表的初始值設定項子句組成的引數集為基礎,但限制只允許非窄化轉換。如果此階段為複製列表初始化產生了顯式建構函式作為最佳匹配,則編譯失敗(注意,在簡單的複製初始化中,根本不考慮顯式建構函式)。
  • 否則,如果T是具有固定底層型別U列舉型別,花括號初始值設定項列表只有一個初始值設定項v,並且滿足以下所有條件,則列舉用將v轉換為U的結果初始化:
    • 初始化是直接列表初始化。
    • v標量型別
    • v 可以隱式轉換為U
    • vU的轉換是非窄化的。
(C++17 起)
  • 否則(如果T不是類型別),如果花括號初始值設定項列表只有一個初始值設定項子句,並且T不是引用型別,或者T是引用型別,其引用型別與初始值設定項子句的型別相同或為其基類,則T直接初始化(在直接列表初始化中)或複製初始化(在複製列表初始化中),但窄化轉換不允許。
  • 否則,如果T是與初始值設定項子句的型別不相容的引用型別
  • 型別為 T 引用的臨時 prvalue 被複制列表初始化,並且引用繫結到該臨時物件(如果引用是非 const 左值引用,則此操作失敗)。
(C++17 前)
  • 生成一個 prvalue。該 prvalue 透過複製列表初始化初始化其結果物件。然後,該 prvalue 用於直接初始化引用(如果引用是非 const 左值引用,則此操作失敗)。臨時物件的型別是 T 引用的型別,除非 T 是“引用到未知邊界的 U 型別陣列”,在這種情況下,臨時物件的型別是宣告 U x[] Hx 的型別,其中 H 是初始值列表(C++20 起)
(C++17 起)
  • 否則,如果花括號初始值設定項列表沒有初始值設定項子句,則T值初始化

[編輯] 列表初始化 std::initializer_list

型別為std::initializer_list<E>的物件由初始化列表構造,如同編譯器生成實體化(C++17 起)了型別為“Nconst E的陣列”的prvalue,其中N是初始化列表中的初始值設定項子句的數量;這被稱為初始化列表的後備陣列

後備陣列的每個元素都透過相應初始化列表的初始值設定項子句進行複製初始化,並且std::initializer_list<E>物件被構造以引用該陣列。為複製選擇的建構函式或轉換函式必須在初始化列表的上下文中可訪問。如果初始化任何元素需要窄化轉換,則程式非良構。

後備陣列的生命週期與任何其他臨時物件的生命週期相同,但從後備陣列初始化std::initializer_list物件會像將引用繫結到臨時物件一樣延長陣列的生命週期。

void f(std::initializer_list<double> il);
 
void g(float x)
{
   f({1, x, 3});
}
 
void h()
{
   f({1, 2, 3});
}
 
struct A { mutable int i; };
 
void q(std::initializer_list<A>);
 
void r()
{
    q({A{1}, A{2}, A{3}});
}
 
// The initialization above will be implemented in a way roughly equivalent to below,
// assuming that the compiler can construct an initializer_list object with a pair of
// pointers, and with the understanding that `__b` does not outlive the call to `f`.
 
void g(float x)
{
    const double __a[3] = {double{1}, double{x}, double{3}}; // backing array
    f(std::initializer_list<double>(__a, __a + 3));
}
 
void h()
{
    static constexpr double __b[3] =
        {double{1}, double{2}, double{3}}; // backing array
    f(std::initializer_list<double>(__b, __b + 3));
}
 
void r()
{
    const A __c[3] = {A{1}, A{2}, A{3}}; // backing array
    q(std::initializer_list<A>(__c, __c + 3));
}

所有後備陣列是否不同(即是否儲存在不重疊的物件中)未指定

bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2)
{
    return il2.begin() == il1.begin() + 1;
}
 
bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // the result is unspecified:
                                              // the back arrays can share
                                              // storage within {1, 2, 3, 4}

[編輯] 窄化轉換

列表初始化透過禁止以下轉換來限制允許的隱式轉換

  • 從浮點型別到整數型別的轉換
  • 從浮點型別 T 到另一個浮點型別的轉換,如果其浮點轉換等級既不大於也不等於 T 的等級,除非轉換結果是常量表達式且滿足以下條件之一:
    • 轉換後的值是有限的,並且轉換沒有溢位。
    • 轉換前後的值都是非有限的。
  • 從整數型別到浮點型別的轉換,除非源是一個常量表達式,其值可以精確地儲存在目標型別中
  • 從整數或無作用域列舉型別到整數型別的轉換,如果目標型別不能表示原始型別的所有值,除非
    • 源是一個位域,其寬度w小於其型別(或對於列舉型別,其底層型別)的寬度,並且目標型別可以表示寬度為w且與原始型別具有相同符號性的假設擴充套件整數型別的所有值,或者
    • 源是一個常量表達式,其值可以精確地儲存在目標型別中
  • 從指標型別或指向成員的指標型別到bool的轉換

[編輯] 注意

在花括號初始值設定項列表中,每個初始值設定項子句都在其後繼的任何初始值設定項子句之前定序。這與函式呼叫表示式的引數相反,後者不定序(C++17 前)不定序(C++17 起)

花括號初始值設定項列表不是表示式,因此沒有型別,例如decltype({1, 2})是錯誤的。沒有型別意味著模板型別推導無法推匯出與花括號初始值設定項列表匹配的型別,因此給定宣告template<class T> void f(T);,表示式f({1, 2, 3})是錯誤的。但是,模板引數可以以其他方式推導,例如std::vector<int> v(std::istream_iterator<int>(std::cin), {}),其中迭代器型別由第一個引數推導,但也用於第二個引數位置。使用關鍵字auto的型別推導有一個特殊例外,它在複製列表初始化中將任何花括號初始值設定項列表推導為std::initializer_list

此外,因為花括號初始值設定項列表沒有型別,當它用作過載函式呼叫的引數時,會適用特殊的過載決議規則

聚合型別直接從相同型別的單個初始值設定項子句的花括號初始值設定項列表進行復制/移動初始化,但非聚合型別首先考慮std::initializer_list建構函式

struct X {}; // aggregate
 
struct Q     // non-aggregate
{
    Q() = default;
    Q(Q const&) = default;
    Q(std::initializer_list<Q>) {}
};
 
int main()
{
    X x;
    X x2 = X{x}; // copy-constructor (not aggregate initialization)
 
    Q q;
    Q q2 = Q{q}; // initializer-list constructor (not copy constructor)
}

一些編譯器(例如 gcc 10)在 C++20 模式下,只將從指標或指向成員的指標到bool的轉換視為窄化轉換。

功能測試宏 標準 特性
__cpp_initializer_lists 200806L (C++11) 列表初始化和std::initializer_list

[編輯] 示例

#include <iostream>
#include <map>
#include <string>
#include <vector>
 
struct Foo
{
    std::vector<int> mem = {1, 2, 3}; // list-initialization of a non-static member
    std::vector<int> mem2;
 
    Foo() : mem2{-1, -2, -3} {} // list-initialization of a member in constructor
};
 
std::pair<std::string, std::string> f(std::pair<std::string, std::string> p)
{
    return {p.second, p.first}; // list-initialization in return statement
}
 
int main()
{
    int n0{};  // value-initialization (to zero)
    int n1{1}; // direct-list-initialization
 
    std::string s1{'a', 'b', 'c', 'd'}; // initializer-list constructor call
    std::string s2{s1, 2, 2};           // regular constructor call
    std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char)
 
    int n2 = {1}; // copy-list-initialization
    double d = double{1.2}; // list-initialization of a prvalue, then copy-init
    auto s4 = std::string{"HelloWorld"}; // same as above, no temporary
                                         // created since C++17
 
    std::map<int, std::string> m = // nested list-initialization
    {
        {1, "a"},
        {2, {'a', 'b', 'c'}},
        {3, s1}
    };
 
    std::cout << f({"hello", "world"}).first // list-initialization in function call
              << '\n';
 
    const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array
    int&& r1 = {1}; // binds an rvalue reference to a temporary int
//  int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref
 
//  int bad{1.0}; // error: narrowing conversion
    unsigned char uc1{10}; // okay
//  unsigned char uc2{-1}; // error: narrowing conversion
 
    Foo f;
 
    std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n'
              << s1 << ' ' << s2 << ' ' << s3 << '\n';
    for (auto p : m)
        std::cout << p.first << ' ' << p.second << '\n';
    for (auto n : f.mem)
        std::cout << n << ' ';
    for (auto n : f.mem2)
        std::cout << n << ' ';
    std::cout << '\n';
 
    [](...){}(d, ar, r1, uc1); // has effect of [[maybe_unused]]
}

輸出

world
0 1 1
abcd cd aa
1 a
2 abc
3 abcd
1 2 3 -1 -2 -3

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 1288 C++11 使用包含單個初始值設定項子句的花括號初始值設定項列表初始化引用總是將引用繫結到臨時物件
始終將引用繫結到臨時物件
如果有效,則繫結到該初始值設定項
子句
CWG 1290 C++11 後備陣列的生命週期未正確指定 指定與其他臨時物件相同
臨時物件
CWG 1324 C++11 {}初始化的初始化首先考慮 聚合初始化
首先考慮
CWG 1418 C++11 後備陣列的型別缺少const 新增const
CWG 1467 C++11 禁止聚合和字元陣列的同類型初始化;對於單子句列表,初始化列表建構函式優先於複製建構函式
陣列被禁止;初始化列表建構函式對於單子句列表優先於複製建構函式
優先於複製建構函式
允許同類型初始化;單子句
允許;單子句
列表直接初始化
CWG 1494 C++11 當用不相容型別的初始值設定項子句列表初始化引用時,未指定所建立的臨時物件是直接列表初始化還是複製列表初始化。
建立是直接列表初始化還是複製列表初始化
建立是直接列表初始化還是複製列表初始化
這取決於引用的
初始化型別
對於引用
CWG 2137 C++11 當從{X}列表初始化X時,初始化列表建構函式輸給複製建構函式
當列表初始化X{X}時,初始化列表建構函式輸給複製建構函式
非聚合型別首先考慮
初始化列表
CWG 2252 C++17 列舉可以從非標量值進行列表初始化 已禁止
CWG 2267 C++11 CWG 問題 1494 的解決方案明確指出
臨時物件可以被直接列表初始化
它們在列表初始化引用時是
複製列表初始化的
CWG 2374 C++17 列舉的直接列表初始化允許太多源型別 受限
CWG 2627 C++11 可以將較大整數型別的窄位域提升為較小整數型別,但它仍然是窄化轉換
一個較小的整數型別,但它仍然是一個窄化轉換
它不是一個
窄化轉換
CWG 2713 C++20 對聚合類的引用不能
透過指定初始化器列表進行初始化
允許
CWG 2830 C++11 列表初始化沒有忽略頂層cv限定符 忽略
CWG 2864 C++11 溢位的浮點轉換不是窄化轉換 它們是窄化轉換
P1957R2 C++11 從指標/指向成員的指標到bool的轉換不是窄化轉換
bool不是窄化轉換
被認為是窄化轉換
P2752R3 C++11 生命週期重疊的後備陣列不能重疊 它們可能重疊

[編輯] 另見