聚合初始化
从初始化列表初始化聚合. 它是一种形式的 列表初始化(自 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) 被称为指定初始化器:每个 指定符 必须命名 T 的直接非静态数据成员,并且表达式中使用的所有 指定符 s 必须按 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 中,大小比字符串字面量大小少一个字符的字符数组可以用字符串字面量初始化;结果数组不是以空字符结尾的。在 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++20) (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 不兼容 |
这种初始化 是有效的 |
[edit] 另请参阅
C 文档 用于 结构体和联合体初始化
|