C++11 の便利な機能に継承コンストラクタがありますが、その挙動ではまったのでメモを残します。
継承コンストラクタとは?
C++11 以前において、派生クラスで親クラスのコンストラクタを利用したい場合は、初期化リストを記述する必要があります。例えば以下のようにします。
struct Base {
Base() {}
Base(int) {}
Base(const char* p) {}
};
struct Derived : Base {
Derived() : Base() {}
Derived(int i) : Base(i) {}
Derived(const char* p) : Base(p) {}
};
int main()
{
// デフォルトコンストラクタで初期化
// Derived::Derived() を利用
Derived o1;
// int型の引数を取るコンストラクタで初期化
// Derived::Derived(int) を利用
Derived o2(1);
// const char*型の引数を取るコンストラクタで初期化
// Derived::Derived(const char*) を利用
Derived o3("string");
}
上記のように、利用したい親クラスのコンストラクタを派生クラスの初期化リストに記述します。しかし、この方法だとコンストラクタが多数ある親クラスを利用したい時に冗長です。そこで C++11 から継承コンストラクタが利用できるようになりました。これを利用すると、親クラスのコンストラクタをそのまま利用可能です。以下のように記述します。
struct Base {
Base() {}
Base(int) {}
Base(const char* p) {}
};
struct Derived : Base {
// Base クラスのコンストラクタを継承する
using Base::Base;
};
int main()
{
// デフォルトコンストラクタで初期化
// Base::Base() を利用
Derived o1;
// int型の引数を取るコンストラクタで初期化
// Base::Base(int) を利用
Derived o2(1);
// const char*型の引数を取るコンストラクタで初期化
// Base::Base(const char*) を利用
Derived o3("string");
}
上記のように using 文を利用すれば、すべての親クラスのコンストラクタを暗黙的に利用可能になります。詳しい内容については以下をご覧下さい。
派生クラスでコンストラクタを追加する
継承コンストラクタを利用しつつ、独自のコンストラクタを追加する事もできます。新しいコンストラクタを追加するだけです。
struct Base {
Base() {}
Base(int) {}
Base(const char* p) {}
};
struct Derived : Base {
// Base クラスのコンストラクタを継承する事を宣言する
using Base::Base;
// 新しいコンストラクタ
Derived(int, int) {}
};
int main()
{
// int型の引数を取るコンストラクタで初期化
// Base::Base(int) を利用
Derived o2(1);
// int型の引数を2つ取るコンストラクタで初期化
// Derived::Derived(int, int) を利用
Derived o4(1, 2);
}
独自コンストラクタを追加した時は、デフォルトコンストラクタを追加する必要がある
独自のコンストラクタを追加すると、デフォルトコンストラクタは継承されません。以下のようなプログラムはコンパイル時にエラーになります。
struct Base {
Base() {}
Base(int) {}
Base(const char* p) {}
};
struct Derived : Base {
// Base クラスのコンストラクタを継承する事を宣言する
using Base::Base;
// 新しいコンストラクタ
Derived(int, int) {}
};
int main()
{
// デフォルトコンストラクタで初期化
// Base::Base() を利用するつもり
Derived o1;
// int型の引数を2つ取るコンストラクタで初期化
// Derived::Derived(int, int) を利用
Derived o4(1, 2);
}
コンパイルした時の実行結果は以下の通りです。
[develop@localhost tmp1]$ g++ -g -Wall --std=c++11 sample4.cc
sample4.cc: 関数 ‘int main()’ 内:
sample4.cc:17:13: エラー: no matching function for call to ‘Derived::Derived()’
Derived o1;
^
sample4.cc:17:13: 備考: 候補:
sample4.cc:9:17: 備考: Derived::Derived(int)
using Base::Base;
^
sample4.cc:9:17: 備考: 候補では 1 個の引数が予期されますが、0 個の引数が与えられています
sample4.cc:9:17: 備考: Derived::Derived(const char*)
sample4.cc:9:17: 備考: 候補では 1 個の引数が予期されますが、0 個の引数が与えられています
sample4.cc:11:5: 備考: Derived::Derived(int, int)
Derived(int, int) {}
^
sample4.cc:11:5: 備考: 候補では 2 個の引数が予期されますが、0 個の引数が与えられています
sample4.cc:7:8: 備考: constexpr Derived::Derived(const Derived&)
struct Derived : Base {
^
sample4.cc:7:8: 備考: 候補では 1 個の引数が予期されますが、0 個の引数が与えられています
sample4.cc:7:8: 備考: constexpr Derived::Derived(Derived&&)
sample4.cc:7:8: 備考: 候補では 1 個の引数が予期されますが、0 個の引数が与えられています
検索用に英語版も記載しておきます。
[develop@localhost tmp1]$ LANG=C g++ -g -Wall --std=c++11 sample4.cc
sample4.cc: In function 'int main()':
sample4.cc:17:13: error: no matching function for call to 'Derived::Derived()'
Derived o1;
^
sample4.cc:17:13: note: candidates are:
sample4.cc:9:17: note: Derived::Derived(int)
using Base::Base;
^
sample4.cc:9:17: note: candidate expects 1 argument, 0 provided
sample4.cc:9:17: note: Derived::Derived(const char*)
sample4.cc:9:17: note: candidate expects 1 argument, 0 provided
sample4.cc:11:5: note: Derived::Derived(int, int)
Derived(int, int) {}
^
sample4.cc:11:5: note: candidate expects 2 arguments, 0 provided
sample4.cc:7:8: note: constexpr Derived::Derived(const Derived&)
struct Derived : Base {
^
sample4.cc:7:8: note: candidate expects 1 argument, 0 provided
sample4.cc:7:8: note: constexpr Derived::Derived(Derived&&)
sample4.cc:7:8: note: candidate expects 1 argument, 0 provided
継承コンストラクタと独自コンストラクタ、デフォルトコンストラクタを同時に使用する方法
この問題を回避したい時は、デフォルトコンストラクタを派生クラスで定義しなおせばオーケーです。以下のプログラムはコンパイル可能です。
struct Base {
Base() {}
Base(int) {}
Base(const char* p) {}
};
struct Derived : Base {
// Base クラスのコンストラクタを継承する事を宣言する
using Base::Base;
// デフォルトコンストラクタ
Derived() {}
// 新しいコンストラクタ
Derived(int, int) {}
};
int main()
{
// デフォルトコンストラクタで初期化
// Derived::Derived() を利用
Derived o1;
// int型の引数を取るコンストラクタで初期化
// Base::Base(int) を利用
Derived o2(1);
// const char*型の引数を取るコンストラクタで初期化
// Base::Base(const char*) を利用
Derived o3("string");
// int型の引数を2つ取るコンストラクタで初期化
// Derived::Derived(int, int) を利用
Derived o4(1, 2);
}
検証環境
以下の g++ で検証しました。
[develop@localhost tmp1]$ g++ --version
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
まとめ
C++11 で継承コンストラクタ、派生クラスの独自コンストラクタ、デフォルトコンストラクタの3つを併用したい場合は、派生クラスでデフォルトコンストラクタを定義して下さい。
なお、この問題が C++11 の仕様なのか g++ の固有の問題なのかまでは今回は確認できませんでした。