从父类ctor调用重写的方法

Posted

技术标签:

【中文标题】从父类ctor调用重写的方法【英文标题】:Calling an overridden method from a parent class ctor 【发布时间】:2011-02-23 07:32:40 【问题描述】:

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

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++ 构造函数中调用虚函数 ...parashift.com/c++-faq-lite/ctors.html#faq-10.7 这个问题是一个奇怪的问题。 C# 和 C++ 是不同的语言,所以它们当然有不同的规则。如果它们有相同的规则,那么它们将是相同的语言。为什么应该 C#遵循 C++ 的规则? 我关于Java构造函数和模板方法模式的相关文章:novyden.blogspot.com/2011/08/… 【参考方案1】:

正如您正确指出的,在 C++ 中,对象的类型为 A,直到 A 构造函数完成。对象在构造过程中实际上改变了类型。这就是为什么使用A 类的vtable 的原因,所以调用A::foo() 而不是B::foo()

在 Java 和 C# 中,自始至终都使用最派生类型的 vtable(或等效机制),即使在基类的构造过程中也是如此。所以在这些语言中,B.foo() 会被调用。

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

【讨论】:

您可以在 Java 中将方法标记为final,这样可以防止它们被子类覆盖。我相信 C# 中也有类似的机制(也许是sealed?)。 是的,将构造函数(直接或间接)调用的方法标记为final 是明智的。 C# 中不需要类似的机制,因为在 C# 中,如果您明确声明方法,则方法只有 virtual “在 Java 和 C# 中,自始至终都使用最衍生类型的 vtable(或等效机制)” - 请您解释一下,我知道什么是 vtable。【参考方案2】:

虽然我知道您这样做是为了实验,但请务必注意以下引自 Effective Java 2nd Edition,第 17 条:设计和文档以进行继承,否则禁止它

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

这里有一个例子来说明:

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构造函数调用overrideMe时,Child还没有完成对final int x的初始化,方法得到了错误的值。这几乎肯定会导致错误和错误。

【讨论】:

“所以子类中的重写方法将被调用” - 超类在其构造过程中如何调用子类方法(当子类尚未构造时)? (我理解上面的例子子类初始化没有完成,但我希望这个方法甚至不应该被调用,因为子类还没有被构造) 您的问题是基于错误的假设,即在没有构造派生类的对象的情况下无法调用方法。在 Java 中可以。

以上是关于从父类ctor调用重写的方法的主要内容,如果未能解决你的问题,请参考以下文章

子类可以直接调用父类的函数吗

《类的继承》第3节:方法的重写

从父类对象调用子类方法

从父类方法调用子类方法

从父类调用子组件方法 - Angular

(十八)多态