コンストラクターからポリモーフィック関数を呼び出すことは、ほとんどの OO 言語で最悪の事態を引き起こします。この状況が発生した場合、言語が異なれば動作も異なります。
基本的な問題は、すべての言語で、派生型の前に基本型を構築する必要があることです。さて、問題は、コンストラクターからポリモーフィック メソッドを呼び出すとはどういう意味かということです。あなたはそれがどのように振る舞うことを期待していますか?基本レベルでメソッドを呼び出す (C++ スタイル) か、階層の最下部にある構築されていないオブジェクトでポリモーフィック メソッドを呼び出す (Java 方式) という 2 つの方法があります。
C++ では、Base クラスは、独自の構築に入る前に、そのバージョンの仮想メソッド テーブルを構築します。この時点で、仮想メソッドへの呼び出しは、メソッドの基本バージョンを呼び出すか、階層のそのレベルに実装がない場合に呼び出される純粋な仮想メソッドを生成することになります。Base が完全に構築された後、コンパイラは Derived クラスの構築を開始し、階層の次のレベルの実装を指すようにメソッド ポインタをオーバーライドします。
class Base {
public:
Base() { f(); }
virtual void f() { std::cout << "Base" << std::endl; }
};
class Derived : public Base
{
public:
Derived() : Base() {}
virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
Derived d;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run
Java では、コンパイラは、基本コンストラクターまたは派生コンストラクターに入る前に、構築の最初のステップで同等の仮想テーブルを構築します。意味合いは異なります (そして、私の好みではより危険です)。基本クラスのコンストラクターが派生クラスでオーバーライドされたメソッドを呼び出す場合、その呼び出しは実際には構築されていないオブジェクトのメソッドを呼び出す派生レベルで処理され、予期しない結果が生じます。コンストラクター ブロック内で初期化される派生クラスのすべての属性は、'final' 属性を含め、まだ初期化されていません。クラス レベルで定義されたデフォルト値を持つ要素には、その値が設定されます。
public class Base {
public Base() { polymorphic(); }
public void polymorphic() {
System.out.println( "Base" );
}
}
public class Derived extends Base
{
final int x;
public Derived( int value ) {
x = value;
polymorphic();
}
public void polymorphic() {
System.out.println( "Derived: " + x );
}
public static void main( String args[] ) {
Derived d = new Derived( 5 );
}
}
// outputs: Derived 0
// Derived 5
// ... so much for final attributes never changing :P
ご覧のとおり、ポリモーフィック ( C++ 用語ではvirtual ) メソッドの呼び出しは、よくあるエラーの原因です。C++ では、少なくともまだ構築されていないオブジェクトのメソッドを呼び出さないという保証があります...