命名空间
变体
操作

聚合初始化

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 

从初始化列表初始化聚合. 它是一种形式的 列表初始化(自 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 起)
1,2) 使用普通初始化列表初始化聚合。
3,4) 使用 指定初始化器 初始化聚合(仅限聚合类)。

[编辑] 定义

[编辑] 聚合

聚合 是以下类型之一

  • 数组类型
  • 具有以下特性的类类型
  • 没有用户声明的构造函数
(直到 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

[编辑] 初始化过程

[编辑] 确定元素类型

聚合初始化的效果是

1) 确定聚合体的显式初始化元素,如下所示
  • 如果初始化列表是一个 指定初始化列表(聚合体只能是类类型),则每个指定符中的标识符都必须命名类的直接非静态数据成员,并且聚合体的显式初始化元素是包含这些成员的元素。
(自 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
2) 初始化聚合体的每个元素按元素顺序。也就是说,与给定元素相关联的所有值计算和副作用都 其在顺序中之后的任何元素之前进行(自 C++11 起).

[编辑] 显式初始化的元素

对于每个显式初始化的元素

  • 如果该元素是匿名联合体的成员,并且初始化列表是一个 指定初始化列表,则该元素由指定初始化列表 {D} 初始化,其中 D 是命名匿名联合体成员的成员的指定初始化子句。应该只有一个这样的指定初始化子句。
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
  • 否则,如果初始化列表是一个指定初始化列表,则该元素用相应指定初始化子句的初始化器初始化。
  • 如果该初始化器是 语法 (1),并且需要进行收窄转换才能将表达式转换为目标类型,则程序格式不正确。
(自 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 起)

[编辑] 字符数组

普通字符类型 (charsigned charunsigned char)char8_t(自 C++20 起)char16_tchar32_t(自 C++11 起)wchar_t 数组可以分别从普通 字符串字面量、UTF-8 字符串字面量(自 C++20 起)、UTF-16 字符串字面量、UTF-32 字符串字面量(自 C++11 起) 或宽字符串字面量初始化,可选地用花括号括起来。此外,charunsigned 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 文档 用于 结构体和联合体初始化