【C++】デフォルトコンストラクタを持たない型の配列を初期化する
class X { public: explicit X(int) {} };
上のような、デフォルトコンストラクタのないクラスについて考えてみます。
このようなクラスのオブジェクトは、明示的にコンストラクタを呼ばなければ生成できません。
X x1; // NG X x2(99); // OK X xarray1[3]; // NG X xarray2[3] = { X(10), X(20), X(30) // OK };
C++03 までは、こういう型をクラスのメンバに配列として持ったとき*1とoperator new[]
による確保をするときは、各要素に対して直接コンストラクタを呼ぶ方法がなく、デフォルトコンストラクタが必要でした。
class Y { X xarray[3]; // クラス定義だけならOK }; Y y; // NG X* px = new X[3]; // NG
C++11 以降は、初期化リストがこれらにも対して使えるようになり(ユニバーサル初期化)、以前はできなかった初期化が可能になりました。
class Y { X xarray[3]; public: Y() : xarray{ X(0), X(1), X(2) } {} // 初期化リストが使えるようになった }; Y y; // OK X *px = new X[3]{ X(0), X(1), X(2) }; // OK
ちなみに STL のコンテナは C++03 以前であってもデフォルトコンストラクタを要求しません。
コピーコンストラクタさえあれば大丈夫です。
std::vector<X> v1; // OK:空のコンテナ std::vector<X> v2(3); // NG:デフォルトコンストラクタが必要 std::vector<X> v3(3, X(99)); // OK:コピーコンストラクタを使った初期化 v1.push_back(X(999)); // OK:これもコピーコンストラクタによる v1.emplace_back(9999); // OK:末尾に直接構築(C++11 以降)
余談ですが、配置new を使うという手もあったりします。
これは C++03 以前でも可能です。
class Y { char buffer[sizeof(X) * 10]; // バッファとして利用する領域 X* p; public: Y() : p(reinterpret_cast<X*>(buffer)) { // 配置new を使って buffer 内の領域を初期化する for (int i = 0; i < 10; i++) { ::new(&p[i]) X(i); } } ~Y() { // デストラクタを自分で呼ぶ for (int i = 0; i < 10; i++) { p[9-i].~X(); } } };
ぶっちゃけちゃんと動くか怪しいですが……。
配置new で構築したオブジェクトはデストラクタが自動的に呼ばれないため、明示的にデストラクタを呼んでやる必要があります。
デストラクタを配列の後ろの要素から順に呼んでいるのは、オブジェクトが構築された順とは逆に破棄するという規則に従うためです。