从父类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调用重写的方法的主要内容,如果未能解决你的问题,请参考以下文章