从父类ctor调用重写方法

我尝试从父类的构造函数中调用重写方法,并注意到跨语言的不同行为。

C++回声A.foo()

 class A{ public: A(){foo();} virtual void foo(){cout<<"A.foo()";} }; class B : public A{ public: B(){} void foo(){cout<<"B.foo()";} }; int main(){ B *b = new B(); } 

Java回声B.foo()

 class A{ public A(){foo();} public void foo(){System.out.println("A.foo()");} } class B extends A{ public void foo(){System.out.println("B.foo()");} } class Demo{ public static void main(String args[]){ B b = new B(); } } 

C#回声B.foo()

 class A{ public A(){foo();} public virtual void foo(){Console.WriteLine("A.foo()");} } class B : A{ public override void foo(){Console.WriteLine("B.foo()");} } class MainClass { public static void Main (string[] args) { B b = new B(); } } 

我意识到在C ++对象中是从层次结构中最顶层的父进程创建的,所以当构造函数调用重写方法时,B甚至不存在,因此它调用方法的A’版本。 但是,我不确定为什么我在Java和C#中获得不同的行为(来自C ++)

在C ++中,正如您所正确指出的那样,在A构造函数完成之前,对象的类型为A 该对象实际上在构造期间改变了类型。 这就是使用A类的vtable的原因,因此调用A::foo()而不是B::foo()

在Java和C#中,即使在构造基类期间,也始终使用最派生类型的vtable(或等效机制)。 所以在这些语言中, B.foo()被调用。

请注意,通常不建议从构造函数中调用虚方法。 如果你不是很小心,虚拟方法可能会认为对象是完全构造的,即使情况并非如此。 在Java中,每个方法都是隐式虚拟的,你别无选择。

虽然我知道您正在为实验做这件事,但重要的是要注意以下有效Java第2版,第17项:设计和inheritance文档的引用,否则禁止它

为了允许inheritance,类必须遵守一些限制。 构造函数不得直接或间接调用可覆盖的方法 。 如果违反此规则,将导致程序失败。 超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法。 如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

这是一个例子来说明:

 public class ConstructorCallsOverride { public static void main(String[] args) { abstract class Base { Base() { overrideMe(); } abstract void overrideMe(); } class Child extends Base { final int x; Child(int x) { this.x = x; } @Override void overrideMe() { System.out.println(x); } } new Child(42); // prints "0" } } 

这里,当Base构造函数调用overrideMeChild尚未完成初始化final int x ,并且该方法获取了错误的值。 这几乎肯定会导致错误和错误。