オーバーライド


隠蔽と上書き

サブクラスのメンバの名前がスーパークラスのメンバの名前と衝突している場合、通常はサブクラスによってスーパークラスのメンバが隠蔽されます。 隠蔽は、オブジェクトからメンバを参照したときに、オブジェクトの型からアクセスするべきメンバを判定します。 例えば、A クラスと A クラスを継承する B サブクラスで x という名前のフィールドが衝突している場合、x に参照するクラス型変数が B 型であれば B.x に、A 型であれば A.x にアクセスします。

import std.stream;

class A {
	int x;
}
class B : A {
	int x;
}

int main() {
	B objB = new B();
	A objA = objB;

	objA.x = 10;
	objB.x = 100;

	stdout.printf("X = %d\n" , objB.x);
	stdout.printf("X = %d\n" , (cast(A)objB).x);
	
	return 0;
}

このプログラムでは A クラスとこれを継承する B クラスを宣言し、それぞれのクラスで同じ名前の x フィールドを宣言しています。 B クラスのインスタンスを生成し、B クラス型変数 objB から参照した場合は B.x に、A クラス型変数 objA にキャストして参照した場合は A.x にアクセスしていることを確認することができます。

ただし、メンバ関数の名前が衝突している場合は自動的にオーバーライドされるため、フィールドの隠蔽とは異なります。 D 言語では、すべてのメンバ関数は仮想関数であると定義されています。 仮想関数とは、呼び出すべき関数の位置が実行時に判定される関数で、同一のシンボルから異なる実装にアクセスさせることができます。 結果として、同一のコードで多様な振る舞いを実現することができるため、オブジェクト指向が掲げる多様性に繋がっているのです。

オーバーライドは、呼び出されるべきメンバ関数を上書きすることを表します。 隠蔽はクラス型を参考にアクセスするメンバを決定していましたが、メンバ関数が衝突した場合はオーバーライドされ、呼び出されるべき関数はサブクラスで上書きされたメンバ関数となります。 オーバーライドされた場合、スーパークラスのメンバ関数にアクセスする手段はオーバーライドしたサブクラスのメンバ関数から super 式を用いて呼び出す以外に方法はありません。

import std.stream;

class A {
	void ShowName() {
		stdout.printf("A . ShowName\n");
	}
}
class B : A {
	void ShowName() {
		stdout.printf("B . ShowName\n");
	}
}

int main() {
	A objA = new B();
	objA.ShowName();
	return 0;
}

このプログラムでは、A クラスの ShowName() メンバ関数と、サブクラスである B クラスの ShowName() メンバ関数がシグネチャを含めて衝突しています。 名前だけの衝突であればオーバーロードされましたが、継承関係の中でシグネチャが衝突した場合はオーバーライドされます。 オーバーライドされたメンバ関数は変数の型に関係なく、インスタンスが参照しているメソッドを呼び出します。 A クラス型の変数 objA から ShowName() メンバ関数を呼び出しても、実際に呼び出されるのは B クラスの ShowName() メンバ関数であることを確認できるでしょう。

動的に関数を呼び出すという処置は、一般論から考えてコンパイル時に確定するよりも実行時の負荷が大きくなります。 そのため、仮想関数の呼び出しは通常の関数よりも遅くなると考えられますが、一般的な D コンパイラであればコンパイル時にオーバーライドされている関数を仮想関数と判断し、オーバーライドされていない関数は非仮想関数であると判断して最適化することができるでしょう。

D 言語仕様では名前と引数が同じメンバ関数がサブクラスで宣言されるた時点でオーバーライドされると定義しているため、オーバーライドを宣言する必要はありませんが override 属性という修飾方法が用意されています。 メンバ関数がオーバーライドされることが前提である場合、override を指定することで不要なミスを避けることができると考えることもできます。 代表的なミスとして、サブクラスでオーバーライドしている予定のメンバ関数の名前が打ち込みミスで間違っているということがあります。 override を指定してれば、スーパークラスで仮想関数が宣言されていない場合、オーバーライドできないためコンパイル時にミスを発見することができるでしょう。

override は属性と呼ばれる宣言の修飾方法に分類されます。 属性については後述します。

import std.stream;

class A {
	void ShowName() {
		stdout.printf("A . ShowName\n");
	}
}
class B : A {
	override void ShowName() {
		stdout.printf("B . ShowName\n");
	}
}

int main() {
	A objA = new B();
	objA.ShowName();
	return 0;
}

B クラスの ShowName() メンバ関数に override 属性を指定しているところに注目してください。 スーパークラスが ShowName() メンバ関数を宣言していない場合はコンパイルエラーとなるでしょう。



前のページへ戻る次のページへ