なまもの備忘録

気になったことをつらつらと書いていきます

aggregate typeの初期化子リストによる初期化

C++11から統一初期化記法が登場した。それによって、配列やstd::arrayなどを初期化子リストで初期化することができるようになった。 具体的に言うと、下のようなことができるようになった。

#include<array>
...
int arr[3]{1, 2, 3};
std::array<int,  3> arr2{1,2,3};
...

自作コンストラクタで初期化子リストによる初期化をする場合、明示的コンストラクタを定義したければ、<initializer_list>ヘッダで定義されるstd::initializer_listクラスのオブジェクトを引数にとる、コンストラクタを定義する必要がある。

ただ、自作クラスがaggregate typeだった場合話が少し変わってくる。

aggregate typeとは、ユーザ定義のコンストラクタ、private又はprotectedな非静的データメンバ、基底クラス、仮想関数を一切含まないクラス、と配列のことだ。

この場合、初期化リストはメンバ変数を定義された順番に埋めていく。これも割と直感的な話だ。

気になるのはメンバ変数に配列が含まれる場合だ。この場合はどのように埋められていくのだろうか。気になったので試して見た。コードは以下でコンパイラはgcc4.2.1。

#include <iostream>
#include <array>

class Example
{
  public:
    int i;
    int iarr[3];
    char c;
};

class Example2
{
  public:
    int iarr[3];
    int i;
    char c;
};
    
class Example3
{
  public:
    int i;
    std::array<int,3> iarr;
    char c;
};

class Example4
{
  public:
    int i;
    std::array<std::array<int,2>,3> iarr;//arrayのarrayでも初期化はうまくいくのだろうか。
    char c;
};


int main(){
    Example A{1,2,3,4,'a'};
    Example2 B{1,2,3,4,'b'};
    Example3 C{1,2,3,4,'c'};
    Example4 D{1,21,22,31,32,41,42,'d'};
    
    std::cout << A.i << " ";
    for(auto elem : A.iarr)
    std::cout << elem << " ";
    std::cout << A.c << std::endl;

    std::cout << B.i << " ";
    for(auto elem : B.iarr)
    std::cout << elem << " ";
    std::cout << B.c << std::endl;

    std::cout << C.i << " ";
    for(auto elem : C.iarr)
    std::cout << elem << " ";
    std::cout << C.c << std::endl;

    std::cout << D.i << " ";
    for(auto elem : D.iarr){
    for(auto elem2 : elem)
        std::cout << elem2 << " ";
    }
    std::cout << D.c << std::endl;
    return 0;
}

実行結果は以下のようになった。

$ ./a.out
1 2 3 4 a
4 1 2 3 b
1 2 3 4 c
1 21 22 31 32 41 42 d

メンバ変数が配列だろうと気にせず前から埋めていっている。 arrayが入れ子になっている場合は、さらに内側のarrayの先頭から埋められていく。

ちなみに、以下のコードはコンパイルを通ってしまい、

#include <iostream>
#include <array>

class ExampleA
{
  public:
    int i;
    int iarr[3];
    char c;
};
    
class ExampleB
{
  public:
    int i;
    std::array<int,3> iarr;
    char c;
};

int main(){
    ExampleA A{1,2,3,'a',4};
    ExampleB B{1,2,3,'b',4};

    std::cout << A.i << " ";
    for(auto elem : A.iarr)
    std::cout << elem << " ";
    std::cout << A.c << std::endl;

    std::cout << B.i << " ";
    for(auto elem : B.iarr)
    std::cout << elem << " ";
    std::cout << B.c << std::endl;

    return 0;
}

出力結果は以下のようになる。

$ ./a.out
1 2 3 97  ^D
1 2 3 98  ^D

初期化子リストは便利だけれど、乱用すると意図せぬ初期化が容易に起こってしまうので、何も考えずに使っていいわけではなさそうだ。 当たり前ではあるけれども。