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

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

Virtual functions in constructors, why do languages differ?

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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中,在运行任何构造函数之前,将对象成员初始化为零/空,并且此时对象生命周期开始。因此,当您输入构造函数时,您已经有了一个初始化的对象。

在C ++中,对象生存期仅在构造函数完成时开始(尽管成员变量和基类在开始之前已完全构建)。这解释了调用虚拟函数时的行为,以及在构造函数主体中存在异常的情况下为何不运行析构函数的原因。

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


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

我认为C ++方式更有意义,但是当有人查看您的代码时会导致期望问题。如果您知道这种情况,则不应有意将代码置于这种情况下,以便以后进行调试。


Virtual functions in constructors, why do languages differ?

因为没有一种良好的行为。我发现C ++的行为更有意义(因为首先调用了基类c-tor,因此有理由说他们应该调用基类虚函数-毕竟派生类c-tor尚未运行,因此它可能没有为派生的类虚拟函数设置正确的前提条件。

但是有时候,在我想使用虚函数初始化状态的地方(因此在未初始化状态下调用它们就没关系),C#/ Java的行为就更好了。


我认为C ++在具有"最正确的"行为方面提供了最佳的语义……但是,对于编译器而言,这是更多的工作,并且对于以后阅读它的人来说,代码显然是不直观的。

使用.NET方法时,必须非常限制功能,使其不依赖于任何派生的对象状态。


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

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

再一次,C ++行为的优点是它阻止了像上面这样的构造器的编写。

我认为调用假定构造函数已完成的方法的问题不是C ++的好借口。如果确实存在问题,则将不允许构造函数调用任何方法,因为相同的问题可能适用于基类的方法。

反对C ++的另一点是行为的效率大大降低。尽管构造函数直接知道调用的内容,但是必须为每个类从基础到最后更改vtab指针,因为构造函数可能会调用其他将调用虚函数的方法。根据我的经验,与在构造函数中提高虚拟函数的调用效率相比,这浪费的时间要多得多。

更令人烦恼的是,析构函数也是如此。如果您编写了一个虚拟的cleanup()函数,并且基类的析构函数执行了cleanup(),则它肯定不会达到您的期望。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;


推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    设置虚拟机|电脑怎么设置虚拟机

    设置虚拟机|电脑怎么设置虚拟机,,电脑怎么设置虚拟机如果CPU不支持虚拟化技术的话,可以尝试以下方法:1、CPU不支持虚拟化技术的电脑系统也是

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    将XP系统加载到虚拟硬盘并启动计算机

    将XP系统加载到虚拟硬盘并启动计算机,,注释:您可以将XP系统加载到虚拟硬盘中,并使用它来启动计算机。令人惊奇的是,虚拟硬盘可以启动计算机,然

    虚拟内存是如何不足的

    虚拟内存是如何不足的,,网友:我的主板是通过kt333芯片,CPU athlonxp 2600 +,256MB DDR内存,为了玩游戏,我打开了在BIOS的内存优化的几个选项,但

    探探语言设置|探探怎么设置语言

    探探语言设置|探探怎么设置语言,,1. 探探怎么设置语言打开探探软件,然后就有消息提示的红点,点开就行了!其实这些软件都是挺简单的操作的,都是

    excel常用函数都有哪些?

    excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个