デフォルトコンストラクタは大きなお世話な場合があるので、コンパイラに生成させないようにしようという話。
オブジェクトを生成する際に、なんらかの情報が必至であるようなクラスもある。この場合、デフォルトコンストラクタの存在が邪魔だったりする。しかし、デフォルトコンストラクタがないと面倒なことになる場合がある。
たとえば、
class Hoge {
public:
Hoge(int x) : x_(x) {}
private:
int x_;
};
なんてクラスを考える。
問題1. オブジェクトの配列をつくる場合
オブジェクトの配列を定義する際に、引数を与えることはできない。
Hoge hoges[3]; // NG
Hoge *hoges = new Hoge[3]; // NG
ではどうするか。
定義時に初期化
Hoge hoges[3] = { // OK
Hoge(0),
Hoge(10),
Hoge(20)
};
しかし、この方法も完全ではない。
Hoge *hoges = new Hoge[3];
の場合を扱えないから。
ポインタの配列
(Hoge hoges)[10]; // OK
(Hoge *)hoges = new (Hoge *)[10]
この方法にも問題がある。
- 配列の要素すべてに
delete
をかけないといけない
- ポインタのぶんだけ余計にメモリを消費する
placement new
2つ目の問題は、placement newを導入すれば解決できる (see Item 8)。しかし、placement newを用いるにも問題がある。
- 多くのプログラマは、このイディオムに不慣れである
- デストラクタの呼び出し方法がトリッキーになる
問題2. template-based container class
テンプレートを用いたコンテナクラスは、デフォルトコンストラクタを要求する場合が多い。std::vector
みたいに、慎重に設計されたクラスだと問題ないけど、すべてのクラスがそうだとはいえない。
問題3. 仮想基底クラス
class Hoge {
public:
Hoge(int x) : x_(x);
virtual void some_method();
};
class Fuga : public Hoge {
public:
Fuga(); // NG
Fuga(int x) : Hoge(x) {} // OK
void some_method();
};
ある仮想基底クラスから派生したクラスのコンストラクタは、親クラスがデフォルトコンストラクタを持たないことを知っておかないといけないという問題。
策
class Hoge {
public:
Hoge(int x = DEFAULT_VALUE) : x_(x);
private:
static const int DEFAULT_VALUE;
};
のように、引数にデフォルト値をセットするという方法もある。しかし、これだとちゃんと初期化できているか分からない。なので、このようにコンストラクタを定義しておき、引数が与えられなかった場合は、例外を投げたり強制終了させたりする向きもある。
結論
以上のような問題にうんざりして、コンストラクタにデフォルトコンストラクタをつくってもらいたいと思うかもしれない。しかし、その場合は、メンバがちゃんと初期化されているかをチェックしないといけなくなる。チェックのためのコードが走る時間、コードサイズの増加といったコストを払わないといけない。
なので、初期化しなければいけないメンバをもつクラスで、コンストラクタに引数を与えないと行けないようなものの場合は、デフォルトコンストラクタを使うのは避けよう。そうすることによる弊害を受け入れるのは苦痛かもしれないけれども、ちゃんと初期化されており、無駄なチェックコードが走らないといった利点を享受できるわけだ。
所感
だんだん言語仕様の複雑なところに入り込んできている。
デフォルトコンストラクタを生成させないようにすると、自然なコードが書けないようになる。やっぱりそんな言語はイヤだなぁと思う。