【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 までは、こういう型をクラスのメンバに配列として持ったとき*1operator 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 で構築したオブジェクトはデストラクタが自動的に呼ばれないため、明示的にデストラクタを呼んでやる必要があります。
デストラクタを配列の後ろの要素から順に呼んでいるのは、オブジェクトが構築された順とは逆に破棄するという規則に従うためです。

まとめ

  • デフォルトコンストラクタがない型の配列を作るには、ユニバーサル初期化を利用しよう!
  • C++03 以前の場合は代替案を考えよう(ポインタの配列にするとか)
  • ていうか普通に STL 使おう!

*1:ユーザ定義のコンストラクタを持たず、すべてのメンバ変数が public であるクラスなら、C++03 以前でも波括弧での初期化は可能