FreeBASIC マニュアルのトップに戻る

FreeBASIC ProPgPolymorphism

目次→教本→プログラマーのための案内Inheritance Polymorphism←オリジナル・サイト

継承ポリモーフィズム 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

←リンク元に戻る プログラム開発関連に戻る

継承多型(Inheritance Polymorphism) は、ベース型から派生型のメンバー手続きを呼び出す機能で、処理されたオブジェクトの実際の型を気にする必要はありません。

序文:
継承多型(サブ型多型)は、さまざまな型を持つことができるエンティティに、単一のインターフェースを提供する、という概念です。
より正確には、同じインターフェイスは、同じ継承階層に属する各型で、同じ識別子を持つメンバー手続きによって、実装されます。

「抽象」/「仮想」手続きのおかげで、派生型手続きを自動的に呼び出すベース型のみを使って、コードを記述できます。
そうすれば、オブジェクトの組み込み型を気にせずに、オブジェクトの手続きを呼び出すことができます。

複数の異なる型に同じ手続き名を使うことで、多型は、はるかに汎用的なプログラミング(抽象化)を可能にします。
コーダーは、ベース手続きを呼び出すときに、手続きが適用されるオブジェクトの正確な型を知る必要はありません。
コーダーは、この型が、手続きを実装することを知っていればよいだけです。

たとえば、手続き 'moving()' は、呼び出し時に参照されたインスタンスの、実際の派生型に従って、適切な移動を実行します。
これにより、プログラムは 'instance' の実際の派生型を心配することなく、 'instance.moving()' と言うことができます。

継承多型の動作
基本型から継承する派生型の手続きを再定義する機能は、特殊化と呼ばれます。
そうすれば、オブジェクトの組み込み型を気にせずにオブジェクトの手続きを呼び出すことができます: これが、継承多型です。

これにより、基本型の共通インターフェイスでマスクすることで、オブジェクト・ファミリの特殊な型の詳細を抽象化することができます。

ポインター、または基本型の参照を使って、オブジェクトを指定
インスタンス化型が基本型から派生した型であるオブジェクトのコレクションを考えるとき、これらのオブジェクトをすべて基本型のオブジェクトとみなすことで、統一された方法で操作できます。
さらに、特定の動作は、各オブジェクトのインスタンス化された型に応じて、特殊化できます。
つまり、同じ継承階層の異なるオブジェクトの使用は、これらのオブジェクトの動作が特定のままでも、同種です。

したがって、派生型のインスタンスを指す、基本型のポインターまたは参照を使って、このようなオブジェクトを操作できます。

派生型の特殊な手続きにより、ベース型の、抽象/仮想手続きを上書き
型で抽象/仮想手続きを宣言するには、この型が、組み込みの 'Object' 型を、直接または間接的に「拡張」する必要があります。

派生型は、同じ識別子と署名を持つ、つまり、同じ数と型のパラメータ、同じ呼び出し規約、および存在する場合は同じ戻り型(または 参照またはポインタによる戻りのための、派生型の戻り)の手続きを宣言することにより、そのベース型で宣言された抽象/仮想手続きを上書きできます:
- 通常、基本型の参照/ポインターは、この参照/ポインターが基本型から派生したインスタンス化された型のオブジェクトを参照している場合でも、同じ型または階層の上位の型(コンパイル時の静的バインディング)の手続きにのみアクセスできます。
- しかし、基本型手続きが抽象/仮想の場合、これは、実行中のプログラムに、実際のオブジェクト型に関連して、最も派生した上書き手続きを解決するように指示します。(実行時の動的バインディング)

継承多型の内部のメカニズム
抽象/仮想メンバー手続きは、仮想手続きテーブル(vtbl)を使って実装されます。 vtbl は、簡単に言うと、静的手続きポインタのテーブルです。
コンパイラは、各多形の型、つまり、少なくとも抽象/仮想手続きを定義する型、または前者から派生した型、に対して vtbl を埋めます。
vtbl には、継承階層の上位で定義された抽象/仮想手続きを含む、型で使える、すべての抽象/仮想手続きのエントリが含まれます(抽象手続きがまだ実装されていない場合、null ポインタが vtbl に設定されます)。

各 vtbl には、対応する型の各抽象/仮想手続きのための、正しい手続きのアドレスが含まれています。 ここで正しいとは、その手続きを定義/上書きする、最も派生型の対応する手続きのアドレスを意味します。
型がインスタンス化されると、インスタンスには、インスタンス化された型の仮想手続きテーブル(vtbl)へのポインター(vptr)が含められます。
派生型のオブジェクトが、基本型のポインター/参照内で参照されると、抽象/仮想手続き機能が実際に実行されます。
抽象/仮想手続きの呼び出しは、実行時に何らかの形で変換され、対応する手続きが、基になるオブジェクトの型(ポインタ/参照型ではない)の仮想手続きテーブルから、選択されます。
したがって、どの手続きが呼び出されるかは、ポインタ/参照が指す実際のオブジェクトの型に依存します。 これは、コンパイル時にはわかりません。そのため、抽象/仮想手続きコールは、実行時に決定されます。

したがって、(ポインタまたは参照による)抽象/仮想手続きコールは、通常のコールではなく、パフォーマンス・オーバーヘッドが少しあります。
これは、多数のコールがある場合に、巨大になる可能性があります。
抽象/仮想手続きコールは、vptr 値(インスタンス・データのオフセット 0 にある)でアドレス指定された、適切な vtbl を使うことにより、コンパイラによって他の何かに変換されます。

vptr 値の場合、コンパイラーは、型の構築子で、追加のコードを生成します。 これは通常、ユーザーコードの前に追加されます。
ユーザーが構築子を定義しない場合でも、コンパイラーはデフォルトの構築子を生成し、vptr の初期化はそこにあります。
したがって、多形の型のオブジェクトが作成されるたびに、vptr は正しく初期化され、その型の vtbl を指します。

注:
組み込みの 'Object' 型は、'拡張' 宣言を使って、派生したすべての型の RTTI(実行時型情報)収容も提供します。
RTTI 収容は、実行時に、オブジェクトの実際の型を判別できます。 これは、コンパイル時とは異なる場合があります。
RTTI が、オブジェクトの実際の実行時型名だけでなく、'Object' 組み込み型によりその基本型のすべての型名も提供するため、「演算子 Is(rtti)」は、これを使って、オブジェクトがコンパイル時型から派生した型と互換性があるかどうかを確認します。 それにもかかわらず、RTTI によって保存されるこれらの型名(vtbl 内の特定のポインターによって参照される)は、FreeBASIC キーワードから直接アクセスできない、めちゃくちゃにされた名前です。

制限:
多型は、以下と直接互換性がありません:
- 演算子 'New[]' や 'Delete[]'(配列版の命令文/式/多重定義演算子)。 (実際の型の代わりに)サブタイプ・ポインターを使うと、他の要素(最初の要素を除く)へのアクセスに失敗するため、
- 多重定義演算子 'Delete' でさえ、仮想(静的)として宣言できないため、直接の互換性はありません。

派生型ポインターでこのような演算子 'Delete([])' 命令文を呼び出す代わりに、最も安全な方法は、派生型レベルで演算子 'Delete([])' ステートメントを自動的に起動する、上書きされたユーザー仮想メンバー手続きを(基本型ポインタで)単に呼び出すことです。

例(1).継承多型の学習:「動物型コレクション」
以下に提示する例では、多型のメカニズムに必要なすべての要素を分かりやすく引き出すために、多型の部分が分解されています。

選択される一般的な基本型は、任意の 'animal' (抽象)です。
特殊な派生型は、'dog', 'cat', 'bird' です(それぞれ、その型名を含む非静的文字列メンバーを定義します)。
汎用の基本型で宣言され、各特殊な派生型で定義する必要がある、抽象手続きは次のとおりです:
- 'addr_override_fct()': インスタンスのアドレスを返します。
- 'speak_override_fct()': 話し方を返します。
- 'type_override_sub()': 型名を出力します(initialyzer を持つ文字列メンバーから)。

例(2).継承多型の学習:「グラフ型コレクション」
以下に提示する例では、多型のメカニズムに必要なすべての要素を分かりやすく引き出すために、多型の部分が分解されています。

選択された一般的な基本型は、2つの描画点と色(抽象化)で定義された 'Graphic Form' です。
特殊な派生型は、'Graphic Line', 'Graphic Box', 'Graphic Circle' です(すべて2つの描画点と色で定義されます):
- 'Graphic Line' は、点1と点2をつなぎます。
- 'Graphic Box' には、向かい合う頂点として、点1(上部の左側)と点2(下部の右側)があります。
- 'Graphic Circle' の中心は点1で、点2を通過します。

汎用ベース型で宣言され、各特殊派生型で定義する必要がある、抽象手続きは、描画ウィンドウでの特殊フォームの画像描画です。
2つの描画点と色は、ジェネリックデータなので、混合物に含まれるジェネリック基本型の、3つのジェネリックデータ項目を誘導します。
'graphic point' 型は、x/y 座標値のカプセル化(プライベートとして宣言)で定義され、プロパティによって(定義された描画画面サイズに応じて)それらの有効性を制御します(ただし、これらは公開されます)。

表記法:
- 一般的な基本型名: 'GraphicForm2P'
- 専門派生型名: 'GraphicLine2P', 'GraphicBox2P', 'GraphicCircle2P'
- 仮想手続き名: 'drawGraphicForm2P()'
- 追加の型名(ジェネリック型内の構成で含む): 'GraphicPoint'

参照
プログラマーのための案内に戻る
←リンク元に戻る プログラム開発関連に戻る
ページ歴史:2022-11-29 11:48:28
日本語翻訳:WATANABE Makoto、原文著作者:fxm

ホームページのトップに戻る

表示-非営利-継承