聚合初始化
从初始化器列表初始化聚合。它是列表初始化的一种形式(自 C++11 起)。
目录 |
[编辑] 语法
T 对象 = { arg1, arg2, ... }; |
(1) | ||||||||
T 对象 { arg1, arg2, ... }; |
(2) | (自 C++11 起) | |||||||
T 对象 = { . des1 = arg1 , . des2 { arg2 } ... }; |
(3) | (自 C++20 起) | |||||||
T 对象 { . des1 = arg1 , . des2 { arg2 } ... }; |
(4) | (自 C++20 起) | |||||||
[编辑] 定义
[编辑] 聚合
聚合是以下类型之一
- 数组类型
- 具有以下特性的类类型
|
(直到 C++11) |
(自 C++11 起) (直到 C++20) | |
|
(自 C++20 起) |
- 无私有或受保护的直接非静态数据成员
|
(直到 C++17) |
(自 C++17 起) |
- 无虚成员函数
(自 C++11 起) (直到 C++14) |
[编辑] 元素
聚合的元素是
- 对于数组,是下标递增顺序的数组元素,或
|
(直到 C++17) |
(自 C++17 起) |
[编辑] 归属
花括号括起来的初始化器列表中的每个初始化器子句被称为归属于正在初始化的聚合的元素或其子聚合之一的元素。
考虑到初始化器子句的序列,以及最初形成为正在初始化的聚合的元素序列,并可能如下所述修改的聚合元素序列
- 对于每个初始化器子句,如果满足以下任一条件,则它归属于相应的聚合元素 elem
- elem 不是聚合。
- 初始化器子句以 { 开头。
- 初始化器子句是一个表达式,并且可以形成一个隐式转换序列,将表达式转换为 elem 的类型。
- elem 是一个自身没有聚合元素的聚合。
- 否则,elem 是一个聚合,并且该子聚合在聚合元素列表中被其自身的聚合元素序列替换,并且归属分析从第一个这样的元素和相同的初始化器子句重新开始。换句话说,这些规则递归地应用于聚合的子聚合。
当所有初始化器子句都已用尽时,分析完成。如果剩余任何不归属于聚合或其子聚合之一的元素的初始化器子句,则程序是非良构的。
struct S1 { int a, b; }; struct S2 { S1 s, t; }; // Each subaggregate of “x” is appertained to an initializer clause starting with { S2 x[2] = { // appertains to “x[0]” { {1, 2}, // appertains to “x[0].s” {3, 4} // appertains to “x[0].t” }, // appertains to “x[1]” { {5, 6}, // appertains to “x[1].s” {7, 8} // appertains to “x[1].t” } }; // “x” and “y” have the same value (see below) S2 y[2] = {1, 2, 3, 4, 5, 6, 7, 8}; // The process of the appertainment analysis of “y”: // 1. Initializes the aggregate element sequence (x[0], x[1]) and // the initializer clause sequence (1, 2, 3, 4, 5, 6, 7, 8). // 2. Starting from the first elements of each sequence, // checks whether 1 appertains to x[0]: // · x[0] is an aggregate. // · 1 does not begin with {. // · 1 is an expression, but it cannot be implicitly converted to S2. // · x[0] has aggregate elements. // 3. 0 cannot appertain to x[0], therefore x[0] is replaced by x[0].s and x[0].t, // the aggregate element sequence becomes (x[0].s, x[0].t, x[1]). // 4. Resumes the appertainment check, but 1 cannot appertain to x[0].s either. // 5. The aggregate element sequence now becomes (x[0].s.a, x[0].s.b, x[0].t, x[1]). // 6. Resumes the appertainment check again: // 1 appertains to x[0].s.a, and 2 appertains to x[0].s.b. // 7. The rest of the appertainment analysis works similarly. char cv[4] = {'a', 's', 'd', 'f', 0}; // Error: too many initializer clauses
[编辑] 初始化过程
[编辑] 确定元素种类
聚合初始化的效果是
|
(自 C++20 起) |
- 否则,如果初始化器列表为非空,则聚合的显式初始化的元素是具有归属初始化器子句的元素以及具有带有归属初始化器子句的子聚合的元素。
- 否则,初始化器列表必须为空({}),并且没有显式初始化的元素。
- 如果聚合是联合体并且有两个或多个显式初始化的元素,则程序是非良构的
union u { int a; const char* b; }; u a = {1}; // OK: explicitly initializes member `a` u b = {0, "asdf"}; // error: explicitly initializes two members u c = {"asdf"}; // error: int cannot be initialized by "asdf" // C++20 designated initializer lists u d = {.b = "asdf"}; // OK: can explicitly initialize a non-initial member u e = {.a = 1, .b = "asdf"}; // error: explicitly initializes two members
[编辑] 显式初始化的元素
对于每个显式初始化的元素
struct C { union { int a; const char* p; }; int x; } c = {.a = 1, .x = 3}; // initializes c.a with 1 and c.x with 3
|
(自 C++20 起) |
|
(直到 C++20) |
|
(自 C++20 起) |
- 如果初始化器子句归属于聚合元素,则聚合元素从初始化器子句复制初始化。
- 否则,聚合元素从由所有归属于聚合元素的子对象的初始化器子句组成的花括号括起来的初始化器列表复制初始化,顺序与出现顺序相同。
struct A { int x; struct B { int i; int j; } b; } a = {1, {2, 3}}; // initializes a.x with 1, a.b.i with 2, a.b.j with 3 struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // initializes d1.b1 with 1, d1.b2 with 2, // d1.b3 with 42, d1.d with 4 derived d2{{}, {}, 4}; // initializes d2.b1 with 0, d2.b2 with 42, // d2.b3 with 42, d2.d with 4
[编辑] 隐式初始化的元素
对于非联合体聚合,每个不是显式初始化的元素都按如下方式初始化
|
(自 C++11 起) |
- 否则,如果元素不是引用,则该元素从空初始化器列表复制初始化。
- 否则,程序是非良构的。
struct S { int a; const char* b; int c; int d = b[a]; }; // initializes ss.a with 1, // ss.b with "asdf", // ss.c with the value of an expression of the form int{} (that is, 0), // and ss.d with the value of ss.b[ss.a] (that is, 's') S ss = {1, "asdf"};
如果聚合是联合体且初始化器列表为空,则
|
(自 C++11 起) |
- 否则,联合体的第一个成员(如果有)从空初始化器列表复制初始化。
[编辑] 未知边界的数组
用花括号括起来的初始化器列表初始化的未知边界数组中的元素数量是数组的显式初始化的元素的数量。未知边界的数组不能用 {} 初始化。
int x[] = {1, 3, 5}; // x has 3 elements struct Y { int i, j, k; }; Y y[] = {1, 2, 3, 4, 5, 6}; // y has only 2 elements: // 1, 2 and 3 appertain to y[0], // 4, 5 and 6 appertain to y[1] int z[] = {} // Error: cannot declare an array without any element
指定初始化器语法形式 (3,4) 被称为指定初始化器:每个 designator 必须命名 T 的直接非静态数据成员,并且表达式中使用的所有 designator 的出现顺序必须与 T 的数据成员的顺序相同。 struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // error; designator order does not match declaration order A b{.x = 1, .z = 2}; // ok, b.y initialized to 0 每个由指定初始化器命名的直接非静态数据成员都从紧随指定符的相应花括号或等号初始化器初始化。禁止窄化转换。 指定初始化器可用于将联合体初始化为第一个状态以外的状态。联合体只能提供一个初始化器。 union u { int a; const char* b; }; u f = {.b = "asdf"}; // OK, active member of the union is b u g = {.a = 1, .b = "asdf"}; // Error, only one initializer may be provided 对于非联合体聚合,未提供指定初始化器的元素的初始化方式与上面描述的初始化器子句数量少于成员数量时相同(提供默认成员初始化器时使用默认成员初始化器,否则使用空列表初始化) struct A { string str; int n = 42; int m = -1; }; A{.m = 21} // Initializes str with {}, which calls the default constructor // then initializes n with = 42 // then initializes m with = 21 如果使用指定初始化器子句初始化的聚合具有匿名联合体成员,则相应的指定初始化器必须命名该匿名联合体的成员之一。 注意:C++ 中不允许乱序指定初始化、嵌套指定初始化、混合指定初始化器和常规初始化器以及数组的指定初始化,但在C 编程语言中都允许。 struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order) int arr[3] = {[1] = 5}; // valid C, invalid C++ (array) struct B b = {.a.x = 0}; // valid C, invalid C++ (nested) struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed) |
(自 C++20 起) |
[编辑] 字符数组
普通字符类型(char、signed char、unsigned char),char8_t(自 C++20 起)、char16_t、char32_t(自 C++11 起)或 wchar_t 的数组可以分别从普通字符串字面量、UTF-8 字符串字面量(自 C++20 起)、UTF-16 字符串字面量、UTF-32 字符串字面量(自 C++11 起)或宽字符串字面量初始化,可以选择用花括号括起来。此外,char 或 unsigned char 的数组可以用 UTF-8 字符串字面量初始化,可以选择用花括号括起来(自 C++20 起)。字符串字面量的连续字符(包括隐式终止空字符)初始化数组的元素,如果源值和目标值需要,则进行整型转换(自 C++20 起)。如果指定了数组的大小,并且它大于字符串字面量中的字符数,则剩余字符将进行零初始化。
char a[] = "abc"; // equivalent to char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // Error: initializer string too long unsigned char b[5]{"abc"}; // equivalent to unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // optional braces // equivalent to wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
[编辑] 注释
聚合类或数组可以包含非聚合公共基类(自 C++17 起)、成员或元素,它们按照上述方式初始化(例如,从相应的初始化器子句复制初始化)。
在 C++11 之前,聚合初始化中允许窄化转换,但现在不再允许。
在 C++11 之前,由于语法限制,聚合初始化只能用于变量定义,而不能用于构造函数初始化列表、new-表达式或临时对象创建。
在 C 中,大小比字符串字面量大小小 1 的字符数组可以用字符串字面量初始化;结果数组不是空终止的。C++ 中不允许这样做。
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__cpp_aggregate_bases |
201603L |
(C++17) | 带有基类的聚合类 |
__cpp_aggregate_nsdmi |
201304L |
(C++14) | 带有默认成员初始化器的聚合类 |
__cpp_aggregate_paren_init |
201902L |
(C++20) | 以直接初始化形式的聚合初始化 |
__cpp_char8_t |
202207L |
(C++23) (DR20) |
char8_t 兼容性和可移植性修复(允许从UTF-8 字符串字面量初始化 (unsigned char 数组) |
__cpp_designated_initializers |
201707L |
(C++20) | 指定初始化器 |
[编辑] 示例
#include <array> #include <cstdio> #include <string> struct S { int x; struct Foo { int i; int j; int a[3]; } b; }; int main() { S s1 = {1, {2, 3, {4, 5, 6}}}; S s2 = {1, 2, 3, 4, 5, 6}; // same, but with brace elision S s3{1, {2, 3, {4, 5, 6}}}; // same, using direct-list-initialization syntax S s4{1, 2, 3, 4, 5, 6}; // error until CWG 1270: // brace elision only allowed with equals sign int ar[] = {1, 2, 3}; // ar is int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // too many initializer clauses char cr[3] = {'a'}; // array initialized as {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // fully-braced 2D array: {1, 2} // {3, 4} int ar2d2[2][2] = {1, 2, 3, 4}; // brace elision: {1, 2} // {3, 4} int ar2d3[2][2] = {{1}, {2}}; // only first column: {1, 0} // {2, 0} std::array<int, 3> std_ar2{{1, 2, 3}}; // std::array is an aggregate std::array<int, 3> std_ar1 = {1, 2, 3}; // brace-elision okay // int ai[] = {1, 2.0}; // narrowing conversion from double to int: // error in C++11, okay in C++03 std::string ars[] = {std::string("one"), // copy-initialization "two", // conversion, then copy-initialization {'t', 'h', 'r', 'e', 'e'}}; // list-initialization union U { int a; const char* b; }; U u1 = {1}; // OK, first member of the union // U u2 = {0, "asdf"}; // error: too many initializers for union // U u3 = {"asdf"}; // error: invalid conversion to int [](...) { std::puts("Garbage collecting unused variables... Done."); } ( s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1 ); } // aggregate struct base1 { int b1, b2 = 42; }; // non-aggregate struct base2 { base2() : b3(42) {} int b3; }; // aggregate in C++17 struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4 derived d2{{}, {}, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4
输出
Garbage collecting unused variables... Done.
[编辑] 缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 413 | C++98 | 匿名位域在聚合初始化中被初始化 | 它们被忽略 |
CWG 737 | C++98 | 当字符数组用字符串字面量初始化时 字符数少于数组大小,尾随 '\0' 后的字符元素 未初始化 |
它们是 零初始化 |
CWG 1270 | C++11 | 大括号省略仅允许在复制列表初始化中使用 | 允许在其他地方使用 |
CWG 1518 | C++11 | 声明显式默认构造函数或 具有继承构造函数的类应该是聚合 |
它不是 聚合 |
CWG 1622 | C++98 | 联合体不能用 {} 初始化 | 允许 |
CWG 2149 (P3106R1) |
C++98 | 不清楚大括号省略是否 适用于数组大小推导 |
适用 |
CWG 2272 | C++98 | 未显式声明的非静态引用成员 初始化是从空初始化器列表复制初始化 |
程序是非 良构的在这种情况下 |
CWG 2610 | C++17 | 聚合类型不能具有私有或受保护的间接基类 | 允许 |
CWG 2619 | C++20 | 来自指定初始化器的初始化的种类不明确 | 它取决于 初始化器的种类 |
P2513R4 | C++20 | UTF-8 字符串字面量无法初始化 char 数组 或 unsigned char,这与 C 或 C++17 不兼容 |
这种初始化 是有效的 |
[编辑] 参见
C 文档 关于 Struct 和 union 初始化
|