构造函数中的虚函数,为什么语言不同?

在C ++中,当从构造函数中调用虚函数时,它的行为不像虚函数。

我认为第一次遇到这种行为的每个人都感到惊讶,但第二次认为它是有意义的:

只要尚未执行派生构造函数,该对象就不是 派生实例。

那么如何调用派生函数呢? 前提条件没有机会建立。 例:

class base { public: base() { std::cout << "foo is " << foo() << std::endl; } virtual int foo() { return 42; } }; class derived : public base { int* ptr_; public: derived(int i) : ptr_(new int(i*i)) { } // The following cannot be called before derived::derived due to how C++ behaves, // if it was possible... Kaboom! virtual int foo() { return *ptr_; } }; 

它与Java和.NET完全相同,但他们选择了另一种方式,并且可能是最不惊讶原则的唯一原因?

你认为哪个是正确的选择?

语言如何定义对象的生命周期有根本区别。 在Java和.Net中,对象成员在运行任何构造函数之前初始化为零/ null,此时对象生命周期开始。 所以当你进入构造函数时,你已经有了一个初始化对象。

在C ++中,对象生命周期仅在构造函数完成时开始(尽管成员变量和基类在启动之前完全构造)。 这解释了调用虚函数时的行为,以及构造函数体中存在exception时析构函数未运行的原因。

Java / .Net定义对象生存期的问题在于,确保对象始终满足其不变量时更加困难,而不必在初始化对象但构造函数未运行时放入特殊情况。 C ++定义的问题在于,您有一个奇怪的时期,其中对象处于不确定状态而未完全构造。

两种方式都可能导致意外结果。 最好的办法是不要在构造函数中调用虚函数。

我认为C ++方式更有意义,但当有人审核您的代码时会导致期望问题。 如果你意识到这种情况,你应该故意不要将你的代码放在这种情况下以便以后调试。

构造函数中的虚函数,为什么语言不同?

因为没有一个好的行为。 我发现C ++行为更有意义(因为基类c-tors首先被调用,所以它们应该调用基类虚函数 – 毕竟,派生类c-tor还没有运行,所以它可能没有为派生类虚函数设置正确的前提条件。

但有时候,我想使用虚函数来初始化状态(因此无论是否使用未初始化状态调用它们并不重要),C#/ Java行为更好。

我认为C ++在提供“最正确”行为方面提供了最好的语义……然而,对于编译器来说,这是更多的工作,对于稍后阅读它的人来说,代码肯定是不直观的。

使用.NET方法,函数必须非常有限,不要依赖任何派生的对象状态。

Delphi充分利用了VCL GUI框架中的虚拟构造函数:

 type TComponent = class public constructor Create(AOwner: TComponent); virtual; // virtual constructor end; TMyEdit = class(TComponent) public constructor Create(AOwner: TComponent); override; // override virtual constructor end; TMyButton = class(TComponent) public constructor Create(AOwner: TComponent); override; // override virtual constructor end; TComponentClass = class of TComponent; function CreateAComponent(ComponentClass: TComponentClass; AOwner: TComponent): TComponent; begin Result := ComponentClass.Create(AOwner); end; var MyEdit: TMyEdit; MyButton: TMyButton; begin MyEdit := CreateAComponent(TMyEdit, Form) as TMyEdit; MyButton := CreateAComponent(TMyButton, Form) as TMyButton; end; 

我发现C ++行为非常烦人。 例如,您不能编写虚函数来返回所需的对象大小,并使默认构造函数初始化每个项目。 例如,它会很好:

BaseClass() { for (int i=0; i

然后,C ++行为的优势在于它不鼓励编写如上所述的构造函数。

我不认为调用构造函数的方法已经完成的问题是C ++的一个很好的借口。 如果这确实是一个问题,那么不允许构造函数调用任何方法,因为同样的问题可以应用于基类的方法。

反对C ++的另一个观点是行为效率低得多。 虽然构造函数直接知道它调用了什么,但是必须为从base到final的每个类更改vtab指针,因为构造函数可能会调用其他将调用虚函数的方法。 根据我的经验,通过使构造函数中的虚函数调用更有效,浪费的时间远远超过保存的时间。

令人讨厌的是,析构函数也是如此。 如果你编写一个虚拟的cleanup()函数,并且基类析构函数执行了cleanup(),那么它肯定不会达到预期的效果。

这个以及C ++在退出时调用静态对象上的析构函数这一事实让我很长时间都很生气。