在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中,在运行任何构造函数之前,将对象成员初始化为零/空,并且此时对象生命周期开始。因此,当您输入构造函数时,您已经有了一个初始化的对象。
在C++中,对象生存期仅在构造函数完成时开始(尽管成员变量和基类在开始之前已完全构建)。这解释了调用虚拟函数时的行为,以及在构造函数的主体中存在异常的情况下为何不运行析构函数的原因。
Java/.Net对象生存期定义的问题在于,很难确保对象始终满足其不变性,而不必在初始化对象但构造函数未运行时进行特殊处理。 C++定义的问题在于,您有一个奇数时期,其中对象处于不确定状态且未完全构造。