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