在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++中,对象生命周期仅在构造函数完成时开始(尽管成员变量和基类在启动之前完全构造).这解释了调用虚函数时的行为,以及构造函数体中存在异常时析构函数未运行的原因.
对象生命周期的Java/.Net定义的问题在于,确保对象总是满足其不变量而不必在特定情况下初始化对象但构造函数尚未运行时更难.C++定义的问题在于你有一个奇怪的时期,在这个时期,对象处于不稳定状态而且没有完全构造.
这两种方式都可能导致意外结果.最好的办法是根本不在构造函数中调用虚函数.
我认为C++方式更有意义,但当有人审核您的代码时会导致期望问题.如果你意识到这种情况,你应该故意不要将你的代码放在这种情况下以便以后调试.