C++で書かれたライブラリを読解していて初めて知ったので備忘録として残しておきます。
何か間違いがありましたらコメントか編集リクエストをいただけると幸いです。
はじめに
この記事では using Base::Base
のような継承コンストラクタの説明記事になります。
前置きがいらない方は コンストラクタの継承 から本題へどうぞ。
継承とコンストラクタ
継承とは親クラスのメソッドやプロパティを受け継ぐというオブジェクト指向を構成する重要な概念の1つです。
C++では以下の様にクラスを継承します。
class A {};
class B : public A {};
オブジェクト指向では、継承されたクラスを親(スーパー)クラス、継承するクラスを子(サブ)クラスと呼びます。
子クラスからは親クラスの要素にアクセスできるようになります。
class A {
public:
void display() {
printf("called display\n");
}
};
class B : public A {};
int main() {
B b;
b.display(); // "called display"
}
また、親子クラスのコンストラクタは「親 -> 子」の順序で実行されます。
class A {
public:
A (int a, int b) {
printf("A, %d\n", a + b);
}
void display() {
printf("called display\n");
}
};
class B : public A {
public:
B (int a, int b) : A(a + 1, b + 1) {
printf("B, %d\n", a + b);
}
};
int main() {
B b(1, 2); // "A, 5" "B, 3"
b.display(); // "called display"
}
上記の通り、引数を持つ親クラスのコンストラクタを呼び出す場合は子クラスのコンストラクタに : <SuperClass>(Args)
と書くことで引数を引き渡してコンストラクタを呼び出すことが可能です。
コンストラクタの継承
前節で、引数のある親クラスのコンストラクタの呼び出しに : <SuperClass>(Args)
を使用する方法を紹介しました。
では、親クラスが以下のような状況を考えてみます。
class A {
public:
A (int a) {}
A (int a, char b) {}
A (std::string str) {}
A (int a, int b, int c, int d, int e) {}
A (int a, int b) {
printf("A, %d\n", a + b);
}
void display() {
printf("called display\n");
}
};
class B : public A {
public:
B (int a, int b) : A(a + 1, b + 1) {
printf("B, %d\n", a + b);
}
B (int a) : A(a) {}
B (int a, char b) : A(a, b) {}
B (std::string str) : A(str) {}
B (int a, int b, int c, int d, int e) : A(a, b, c, d, e) {}
};
子クラスでは引数のある親クラスのコンストラクタを呼び出す処理を個別に書かなければいけないため非常に面倒くさいです。
また、子クラスの4つのコンストラクタは引数をそのまま親クラスに引き渡しているだけで、子クラスのコンストラクタが特別に何かの処理をしているわけではありません。
このような子クラスから親クラスのコンストラクタに引数を転送するようなコードを省略して簡単に書けるような機能としてC++11から継承コンストラクタという機能が導入されました。
継承コンストラクタでは using A::A のように using
キーワードの後に親クラス名とコンストラクタ名を::
区切りで記述することで親クラスのコンストラクタを直接呼び出せるようになります。
class A {
public:
A (int a) {}
A (int a, char b) {}
A (std::string str) {}
A (int a, int b, int c, int d, int e) {}
A (int a, int b) {
printf("A, %d\n", a + b);
}
void display() {
printf("called display\n");
}
};
class B : public A {
public:
using A::A; // 継承コンストラクタ
// コンストラクタ毎に個別の処理を書くこともできる。
B (int a, int b) : A(a + 1, b + 1) {
printf("B, %d\n", a + b);
}
};
int main() {
B b1(1, 2);
B b2(1, 2, 3, 4, 5);
b1.display(); // "called display"
}
このように子クラスに using A::A;
という記述を追加することで引数を転送するだけのコンストラクタを省略できるようになりました。
ただし、これまでと同じ処理を書くことでコンストラクタ毎に個別の処理を定義することも可能です。