Virtual functions in constructors, why do languages differ?在C ++中,从构造函数内部调用虚拟函数时,它的行为不像虚拟函数。 我认为第一次遇到这种行为的每个人都会感到惊讶,但第二次认为这是有道理的: 只要未执行派生的构造函数,该对象还不是派生实例。 那么如何调用派生函数呢? 前提条件还没有建立的机会。 例:
Java和.NET完全相同,但是他们选择了另一种方法,这可能是产生最少惊讶原则的唯一原因吗? 您认为哪个是正确的选择? 语言如何定义对象的生存时间存在根本差异。在Java和.Net中,在运行任何构造函数之前,将对象成员初始化为零/空,并且此时对象生命周期开始。因此,当您输入构造函数时,您已经有了一个初始化的对象。 在C ++中,对象生存期仅在构造函数完成时开始(尽管成员变量和基类在开始之前已完全构建)。这解释了调用虚拟函数时的行为,以及在构造函数主体中存在异常的情况下为何不运行析构函数的原因。 Java / .Net对象生存期定义的问题在于,很难确保对象始终满足其不变性,而不必在初始化对象但构造函数未运行时进行特殊处理。 C ++定义的问题在于,您有一个奇数时期,其中对象处于不确定状态且未完全构造。 两种方式都可能导致意外结果。最好的选择是根本不要在构造函数中调用虚函数。 我认为C ++方式更有意义,但是当有人查看您的代码时会导致期望问题。如果您知道这种情况,则不应有意将代码置于这种情况下,以便以后进行调试。
因为没有一种良好的行为。我发现C ++的行为更有意义(因为首先调用了基类c-tor,因此有理由说他们应该调用基类虚函数-毕竟派生类c-tor尚未运行,因此它可能没有为派生的类虚拟函数设置正确的前提条件。 但是有时候,在我想使用虚函数初始化状态的地方(因此在未初始化状态下调用它们就没关系),C#/ Java的行为就更好了。 我认为C ++在具有"最正确的"行为方面提供了最佳的语义……但是,对于编译器而言,这是更多的工作,并且对于以后阅读它的人来说,代码显然是不直观的。 使用.NET方法时,必须非常限制功能,使其不依赖于任何派生的对象状态。 我发现C ++的行为非常烦人。例如,您不能将虚拟函数编写为返回对象的所需大小,并使默认构造函数初始化每个项目。例如,这样做会很不错:
再一次,C ++行为的优点是它阻止了像上面这样的构造器的编写。 我认为调用假定构造函数已完成的方法的问题不是C ++的好借口。如果确实存在问题,则将不允许构造函数调用任何方法,因为相同的问题可能适用于基类的方法。 反对C ++的另一点是行为的效率大大降低。尽管构造函数直接知道调用的内容,但是必须为每个类从基础到最后更改vtab指针,因为构造函数可能会调用其他将调用虚函数的方法。根据我的经验,与在构造函数中提高虚拟函数的调用效率相比,这浪费的时间要多得多。 更令人烦恼的是,析构函数也是如此。如果您编写了一个虚拟的cleanup()函数,并且基类的析构函数执行了cleanup(),则它肯定不会达到您的期望。 这个事实以及C ++在退出时在静态对象上调用析构函数的事实真的让我很生气。 Delphi在VCL GUI框架中充分利用了虚拟构造函数:
|