聚合初始化
使用初始化列表初始化聚合类型。它是一种 列表初始化(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-expression 或临时对象创建,由于语法限制。
在 C 语言中,大小比字符串字面量小一的字符数组可以用字符串字面量初始化;结果数组不是空终止的。这在 C++ 中不允许。
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
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 文档 关于 结构体和联合体初始化
|