クラスは、他のクラスを継承することで基本機能を維持しながら新しい機能を付加することができます。 このとき、基本となる継承元のクラスをスーパークラスと呼び、スーパークラスを継承する新しいクラスをサブクラスと呼びます。 他のクラスを継承する新しいクラスを宣言するには、クラス名の直後にスーパークラスを指定しなければなりません。
class クラス名 : スーパークラス名 { メンバ宣言 ... }
スーパークラスは必ず 1 つだけを指定し、複数のクラスを継承することはできません。 クラスを継承した場合、サブクラスはスーパークラスのメンバをデフォルトで備えられていると解釈することができます。 特定のクラスの機能をそのまま保持し、さらに機能を追加する必要がある場合はクラスを継承します。
import std.stream; class A { int a; void FuncA() { stdout.printf("A . FuncA() : a = %d\n" , a); } } class B : A { int b; void FuncB() { stdout.printf("B . FuncB() : a = %d , b = %d\n" , a , b); } } int main() { B objB = new B(); objB.a = 10; objB.b = 100; objB.FuncA(); objB.FuncB(); return 0; }
このプログラムの B クラスは、A クラスを継承しています。 B クラスは A クラスを継承しているため、スーパークラスのフィールドとメンバ関数を利用することができます。 main() 関数で B クラスのオブジェクトを作成し、a フィールドや FuncA() メンバ関数を呼び出していることがわかります。
B クラスはスーパークラスの機能に加えて、b フィールドと FuncB() メンバ関数など、新しいメンバを宣言することができます。 このとき、サブクラスのメンバ関数からも、スーパークラスのメンバにアクセスすることができます。
サブクラスのスコープ内では、スーパークラスへの参照を表す super 式 を使うことができます。 super 式は this 式と同様にクラス内のメンバ関数やコンストラクタなどのビヘイビアが自分自身を呼び出しているオブジェクトの参照です。 明示的にスーパークラスのメンバであることを記述したい場合にこれを利用することができます。
import std.stream; class A { int a; } class B : A { int a; void ShowA() { stdout.printf("this.a = %d, super.a = %d\n" , this.a , super.a); } } int main() { B obj = new B(); obj.a = 100; obj.ShowA(); return 0; }
このプログラムでは、数値型変数のフィールド a を持つ A クラスと、これを継承し同一の名前のフィールドを宣言しているクラス B を宣言しています。 スーパークラスとサブクラスで a フィールドが衝突していますが、この場合は内側のスコープの識別子が優先されます。 つまり、B クラス内のメンバ関数で a と指定した場合は B クラスの a フィールドが参照されます。 B クラスのスコープから A クラスの a フィールドにアクセスしなければならない場合は super 式を使ってスーパークラスを参照することで、明示的に指定しなければならないのです。
サブクラスが初期化されるとき、コンストラクタの呼び出す順番が重要になります。 当然、何らかの形でスーパークラスのコンストラクタを呼び出す必要があるのですが、問題はサブクラスの初期化を先にするべきか、スーパークラスの初期化を先にするべきかです。
構造上、サブクラスがスーパークラスの機能にアクセスする可能性はありますが、スーパークラスがサブクラスの機能にアクセスする可能性はありません。 だとすれば、サブクラスのコンストラクタが先に実行されると、適切に初期化されていないスーパークラスの機能にアクセスされるのは問題です。 そこで、コンストラクタはスーパークラスから順に呼び出されていきます。
import std.stream; class A { this() { stdout.printf("A クラスのコンストラクタ\n"); } } class B : A { this() { stdout.printf("B クラスのコンストラクタ\n"); } } class C : B { this() { stdout.printf("C クラスのコンストラクタ\n"); } } int main() { new C(); return 0; }
このプログラムは、A クラスを継承する B クラス、B クラスを継承する C クラスを宣言し、main() 関数で C クラスのインスタンスを生成しています。 このとき、C クラスを初期化するコンストラクタがどのような順番で呼び出されるのかを確認することができます。 標準出力には、A クラスのコンストラクタから順に B クラス、C クラスのコンストラクタが呼び出されていることを表す文字が出力されるでしょう。
ただし、この方法ではスーパークラスの引数を受けるコンストラクタに値を渡すことができません。 自動的にスーパークラスのコンストラクタから順番に初期化されるのは、サブクラスが明示的にスーパークラスのコンストラクタを呼び出さない場合だけです。 サブクラスのコンストラクタは super() という名前でスーパークラスのコンストラクタを明示的に呼び出すことができます。 サブクラスが明示的にスーパークラスのコンストラクタを呼び出す場合、サブクラスのコンストラクタから順に初期化されます。 サブクラスのコンストラクタがスーパークラスのコンストラクタを呼び出さない場合は、暗黙的にサブクラスのコンストラクタの先頭に super() が挿入され、結果としてスーパークラスのコンストラクタから初期化されていたのです。
import std.stream; class Color { int r , g , b; this(int r , int g , int b) { this.r = r; this.g = g; this.b = b; stdout.printf("Color クラスのコンストラクタ\n"); } } class ColorEx : Color { int alpha; this(int r, int g , int b , int a) { this.alpha = a; stdout.printf("ColorEx クラスのコンストラクタ\n"); super(r , g , b); } } int main() { ColorEx color = new ColorEx(200 , 255 , 80 , 127); stdout.printf("r=%d, g=%d, b=%d, a=%d\n" , color.r , color.g , color.b , color.alpha); return 0; }
このプログラムでは、Color クラスを継承する ColorEx クラスのコンストラクタで、明示的にスーパークラスのコンストラクタを super() で呼び出しています。 そのため、ColorEx クラスのコンストラクタが初期化を終了した後に Color クラスのコンストラクタが呼び出されることを確認することができるでしょう。
サブクラスのオブジェクトが破棄されると、サブクラスのデストラクタから順にスーパークラスのデストラクタが呼び出されます。 サブクラスのデストラクタが実行されている時点では、スーパークラスの機能は破棄されていません。 また、コンストラクタとは異なり、デストラクタからスーパークラスのデストラクタを明示的に呼び出すことはできません。
import std.stream; class A { ~this() { stdout.printf("A クラスのデストラクタ\n"); } } class B : A { ~this() { stdout.printf("B クラスのデストラクタ\n"); } } class C : B { ~this() { stdout.printf("C クラスのデストラクタ\n"); } } int main() { C obj = new C(); delete obj; return 0; }
このプログラムを実行すると、デストラクタが呼びだれる順番がサブクラスからであることを確認することができます。 delete で明示的に生成した C クラスのオブジェクトを解放すると、C クラス、B クラス、A クラスの順番でデストラクタが実行されます。