aggregate data type(aggregate とも)。

C++では、配列や、特定の条件を満たすクラス型(C++)struct / class / union)を指す。

実用上は、「コンストラクタ呼び出しではなく、{} によってメンバを順番に初期化できる型」と捉えると分かりやすい。

クラス型の集成体

クラス型が集成体であるためには、少なくとも次のような制約がある。

  • ユーザー宣言されたコンストラクタを持たない
  • private または protected の非静的データメンバを持たない
  • virtual 関数を持たない

また、基底クラスやその他の細かな条件もあるため、厳密な定義はC++の版によって少し異なる。 ただし、メンバ変数だけを持つ単純な struct は集成体であることが多い。

集成体初期化

集成体は、リスト初期化によってメンバを宣言順に初期化できる。 これを**集成体初期化(aggregate initialization)**という。

struct Employee
{
    int id {};
    int age {};
    double wage {};
};
 
int main()
{
    Employee frank = { 1, 32, 60000.0 }; // コピーリスト初期化
    Employee joe { 2, 28, 45000.0 };     // リスト初期化
}

通常は、非コピー形式の Employee joe { ... }; のほうが好まれる。

初期化子が足りない場合

初期化子の個数がメンバ数より少ない場合、残りのメンバは次のように初期化される。

多くの場合、後者は値初期化になる。

struct Employee
{
    int id {};
    int age {};
    double wage { 76000.0 };
    double bonus;
};
 
int main()
{
    Employee joe { 2, 28 };
    // joe.id = 2
    // joe.age = 28
    // joe.wage = 76000.0
    // joe.bonus = 0.0
}

そのため、空の {} を使うと、集成体の各メンバをまとめて初期化しやすい。

Employee joe {};

指示付き初期化

C++20では、初期化時にどのメンバへ値を入れるかを明示できる。

struct Foo
{
    int a {};
    int b {};
    int c {};
};
 
int main()
{
    Foo f1{ .a{ 1 }, .c{ 3 } };
    Foo f2{ .a = 1, .c = 3 };
}

指定しなかったメンバは通常どおり初期化される。

ただし、メンバの指定順は宣言順に従う必要がある。

Foo f3{ .b{ 2 }, .a{ 1 } }; // エラー

代入

集成体オブジェクトには、{} を使った代入もできる。

struct Employee
{
    int id {};
    int age {};
    double wage {};
};
 
int main()
{
    Employee joe { 1, 32, 60000.0 };
    joe = { 1, 33, 66000.0 };
}

C++20 では、指示付き初期化を使った代入も書ける。

Employee joe { 1, 32, 60000.0 };
joe = { .id = joe.id, .age = 33, .wage = 66000.0 };

同じ型のオブジェクトで初期化する

集成体も通常のクラス型と同様に、同じ型の別オブジェクトから初期化できる。

struct Foo
{
    int a {};
    int b {};
    int c {};
};
 
int main()
{
    Foo foo { 1, 2, 3 };
 
    Foo x = foo;
    Foo y(foo);
    Foo z { foo };
}

補足

  • 集成体は struct に多いが、classunion でも条件を満たせば集成体になれる
  • std::array も集成体である
  • C++の版によって集成体の厳密な定義は少し変わる

参考