施工期间的虚拟功能。为什么Java不同于C++; 今天我做了一个测试,其中一个问题是在C++构造函数中使用虚拟方法。我没有通过这个问题,我回答说应该没有任何问题,但是在阅读之后我发现我错了
所以我理解不允许这样做的原因是因为派生对象没有完全初始化,因此调用它的虚方法可能导致无效的结果施工期间的虚拟功能。为什么Java不同于C++; 今天我做了一个测试,其中一个问题是在C++构造函数中使用虚拟方法。我没有通过这个问题,我回答说应该没有任何问题,但是在阅读之后我发现我错了,java,c++,Java,C++,所以我理解不允许这样做的原因是因为派生对象没有完全初始化,因此调用它的虚方法可能导致无效的结果 我的问题是,它是如何在Java/C中解决的?我知道我可以在我的基建中调用派生方法,我假设这些语言有完全相同的问题。< P> java和C++有一个非常不同的对象模型。在Java中,变量不能是类类型的对象——相反,只能引用(类类型的)对象。因此,一个类的所有成员(仅为引用)开始时通常为null,直到在内存中设置完整个派生对象。只有这样,构造函数才能运行。因此,当基构造函数调用虚函数时,即使该函数被重写,
我的问题是,它是如何在Java/C中解决的?我知道我可以在我的基建中调用派生方法,我假设这些语言有完全相同的问题。< P> java和C++有一个非常不同的对象模型。在Java中,变量不能是类类型的对象——相反,只能引用(类类型的)对象。因此,一个类的所有成员(仅为引用)开始时通常为
null
,直到在内存中设置完整个派生对象。只有这样,构造函数才能运行。因此,当基构造函数调用虚函数时,即使该函数被重写,被重写的函数至少可以正确地引用派生类的成员。(这些成员本身可能还没有被分配,但至少他们是存在的。)
<>(如果它有帮助,你也可以考虑java中没有<代码>最终< /COD>成员的类在技术上是默认可构造的,至少在原理上是这样的:不像C++,java没有常量或引用(必须在C++中初始化)之类的东西。,实际上根本没有初始值设定项列表。Java中的变量根本不需要初始化。它们要么是以0开头的原语,要么是以
null
开头的类类型引用。一个例外来自非静态final
类成员,它们不能反弹,必须实际“初始化”通过在每个构造函数中的某处精确地有一个赋值语句[感谢@josefx指出这一点!])每个Java构造函数看起来如下所示:
class Foo extends Bar {
Foo() {
super(); // creates Bar
// do things
}
}
因此,如果您将处理派生方法的代码放在do things
中,似乎符合逻辑,即在super()中调用其构造函数后,此基本对象已正确初始化代码> < P>我认为java/C++避免了从派生类反向构造而不是C++从基类转发中解决这个问题。
Java在类构造函数中隐式调用super(),因此,当派生类构造函数中的第一行编写代码被调用时,所有继承类的构造函数都保证已被调用,因此新实例将被完全初始化
我也认为C++中,一个类的新实例以基类开始生命,当我们继承继承链时,它被“升级”到最后类类型。这意味着,当您在构造函数中调用虚函数时,实际上是在为基类调用该函数的版本
在爪哇中,假设C是一个新实例,它将生命作为所需的类类型,因此将调用虚拟方法的正确版本。
< P> C++中的每个多态类(至少有一个虚拟函数的类)在其开始时都有一个隐藏指针(通常称为V表或类似的东西)。它将被初始化为虚拟表(指向每个虚拟函数的主体的函数数组),并且当调用虚拟函数时,C++只调用<代码>((V-TAB*)类)[函数的索引](函数参数)< /代码>,所以,如果您在基类构造函数v-table中调用一个虚拟函数,则指向基类的虚拟表,因为您的类是基类,它仍然需要一些初始化才能成为子类,因此您将从基类而不是从子类调用函数的实现,如果这是一个纯虚拟函数,您将获得一个访问权限违反。
但在java中,这不是这样的,在java中,整个类类似于std::map
在这种情况下JValue
是某种变体类型(例如union或boost::variant
),当您在基构造函数中调用函数时,它会在映射中找到函数名并调用它,它仍然不是来自子级的值,但您仍然可以调用它,如果您在原型中更改了它,因为原型是在构造函数之前创建的,您可以从子级成功调用函数,但如果函数需要从子级构造函数进行初始化,您仍然会得到错误或无效结果。
所以一般来说,在基类中从子函数(例如虚拟函数)调用函数不是一个好的做法。如果您的类需要这样做,请添加一个initialize方法并从子类的构造函数中调用它。Java并不能完全避免这个问题
从依赖于子类字段的超类构造函数调用的重写方法将在初始化这些字段之前调用
如果您控制整个类层次结构,那么当然可以确保覆盖不依赖于子类字段。但是不从构造函数调用虚拟方法更安全
请理解,不允许这样做的原因是因为派生对象未完全初始化,因此调用其虚拟方法可能会导致无效结果
错。C++将调用基类实现方法,而不是派生类。不存在“无效后果”。避免构造的唯一有效原因是,该行为有时会令人惊讶
这与Java不同,因为Java调用派生类的实现。代码看起来很奇怪:1)“类”拼写错误,2)Foo
似乎没有继承任何内容,3)OP关心基构造函数中的虚拟函数。@Kerrek,谢谢你的建议,我已经编辑了