基底クラス コンストラクタの仮想メソッドの呼び出し

提供: Appmethod Topics
移動先: 案内検索

RTL に対する言語サポート(C++) への移動


VCL 基底クラスのコンストラクタ本体から呼び出された仮想メソッド、つまり Object Pascal に実装されたクラスは、C++ の場合と同様に、オブジェクトの実行時型に従って、ディスパッチされます。 Appmethod C++ ではオブジェクトの実行時型を即座に設定する Object Pascal モデルが、派生クラスが構築される前に、基底クラスを構築する C++ モデルと結合されるので、VCL スタイルのクラスに対する基底クラスのコンストラクタから仮想メソッドを呼び出すと、軽微な副作用が発生することがあります。 この副作用の影響を次に説明し、少なくとも 1 つの基底クラスから派生するインスタンス化クラスの例を示します。 この説明では、インスタンス化クラスは派生クラスとも呼びます。

Object Pascal モデル

Object Pascal では、プログラマは inherited キーワードを使用できます。これにより、派生クラスのコンストラクタ本体(内の任意の場所)から基底クラスのコンストラクタを柔軟に呼び出すことができます。 結果として、オブジェクトのセットアップ(データ メンバの初期化)により決まる任意の仮想メソッドを派生クラスがオーバーライドする場合は、基底クラスのコンストラクタが呼び出され、仮想メソッドが呼び出される前にこれが発生することがあります。


C++ モデル

C++ 構文には、派生クラスの構築中(の任意の時点)に基底クラスのコンストラクタを呼び出すための inherited キーワードがありません。 C++ モデルでは、オブジェクトの実行時型は派生クラスではなく、構築中の現在のクラスの型であるから inherited の使用は必要ありません。 したがって、呼び出される仮想メソッドは、派生クラスではなく、現在のクラスのメソッドです。 結果として、これらのメソッドを呼び出す前に、データ メンバの初期化(派生クラス オブジェクトのセットアップ)が必要ありません。


Appmethod C++ モデル

Appmethod C++ の VCL スタイルのオブジェクトには、基底クラスのコンストラクタに対するすべての呼び出し中に、派生クラスの実行時型があります。 したがって、基底クラスのコンストラクタが仮想メソッドを呼び出す場合は、派生クラスがオーバーライドすると、派生クラスのメソッドが呼び出されます。 この仮想メソッドが、初期化リスト内の対象または派生クラスのコンストラクタ本体に依存している場合は、これが発生する前にメソッドが呼び出されます。 たとえば、CreateParams が、TWinControl のコンストラクタで間接的に呼び出される仮想メンバ関数です。 TWinControl からクラスを派生し、コンストラクタにある対象に依存するように CreateParams をオーバーライドする場合は、このコードは CreateParams が呼び出された後に処理されます。 この状況が該当するのは基底クラスの任意の派生クラスです。 A から派生する B があり、その B からさらに派生するクラス C を考えます。 B がメソッドをオーバーライドするが、C がしない場合に、C、A のインスタンスを作成すると、B のオーバーライド メソッドも呼び出されます。

コンストラクタで明示的には呼び出されず、間接的に呼び出される CreateParams のような仮想メソッドに注意してください。


例: 仮想メソッドの呼び出し

次の例では、仮想メソッドをオーバーライドする C++ クラスと VCL スタイルのクラスを比較します。 この例で示すのは、両方の場合に基底クラスのコンストラクタからの仮想メソッド呼び出しを解決する方法です。 MyBase と MyDerived は標準 C++ クラスです。 MyVCLBase と MyVCLDerived は TObject から派生する VCL スタイルのクラスです。 仮想メソッド what_am_I() は両方の派生クラスでオーバーライドされるが、派生クラスのコンストラクタ内ではなく、基底クラスのコンストラクタ内のみで呼び出されます。

# include  <sysutils.hpp > 
# include  <iostream.h > 
// non-VCL style classe s 
class  MyBase  { 
public : 

MyBase() { what_am_I(); } 
virtual void  what_am_I() { cout << "I am a base" << endl; } 
}; 

class  MyDerived :  public  MyBase { 
public : 

virtual void  what_am_I() { cout << "I am a derived" << endl; } 

} ; 
// VCL style classe s 
class  MyVCLBase :  public  TObject  { 
public : 

__fastcall  MyVCLBase() { what_am_I(); } 

virtual void __fastcall  what_am_I() { cout << "I am a base" << endl; } };  class  MyVCLDerived :  public  MyVCLBase {  public : 
virtual void __fastcall  what_am_I() { cout << "I am a derived" << endl; } };  int  main( void ) { 
MyDerived d;// instantiation of the C++ class MyVCLDerived *pvd = new MyVCLDerived;// instantiation of the VCL style class  return  0; 

} 

この例の出力は次のとおりです。

I am a base 
I am a derived 

各基底クラスのコンストラクタへの呼び出し中に MyDerived と MyVCLDerived の実行時型に差分があるためです。


仮想関数に対するデータ メンバのコンストラクタの初期化

データ メンバは仮想関数で使用できるので、初期化時期と方法を理解することは重要です。 Object Pascal で、すべての初期化されていないデータはゼロで初期化されます。 たとえば、これが該当するのはコンストラクタが派生から呼び出されない基底クラスです。 標準 C++ では、初期化されていないデータ メンバの値は保証されません。 クラス データ メンバの次の型は、クラスのコンストラクタの初期化リストで初期化される必要があります。

  • 参照
  • デフォルト コンストラクタがないデータ メンバ


ただし、これらのデータ メンバの値(コンストラクタ本体で初期化された値)は、基底クラスのコンストラクタが呼びされたときに未定義です。 Appmethod C++ では、VCL スタイルのクラスのメモリはゼロで初期化されます。

技術的には、ゼロ(各ビットがゼロ)である VCL クラスのメモリであり、値は実際には未定義です。 たとえば、参照はゼロです。

コンストラクタ本体または初期化リストで初期化されたメンバ変数の値に依存する仮想関数は、変数がゼロで初期化されたように動作します。 これは、基底クラスのコンストラクタが呼び出されてから、初期化リストが処理されるか、コンストラクタ本体に入るからです。 次に例を示します。

# include  <sysutils.hpp> 

class  Base :  public  TObject { 

public : 
__fastcall  Base() { init();  } 
virtual void __fastcall  init() {  } 

}; 

class  Derived :  public  Base { 

public : 
Derived( int  nz) : not_zero(nz) {  } 
virtual void __fastcall  init( ) 
{ 

if  (not_zero == 0 ) 
throw  Exception("not_zero is zero!") ; 
} 
private : 
int  not_zero ; 
} ; 

int  main( void ) 

{ 
Derived *d42 =  new  Derived(42) ; 
return  0 ; 

} 

この例では、Base のコンストラクタで例外が送出されます。 Base が Derived の前に構築されるので、not_zero は、コンストラクタに渡される値 42 で初期化されていません。 基底クラスのコンストラクタが呼び出される前に、ユーザーの VCL スタイル クラスのデータ メンバを初期化できないことに注意してください。

関連項目