代码
#include <string>
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
class Base{
public:
Base(){}
Base(bool arg)
{
foo();
}
virtual void foo(int i = 42)
{
cout << "Base,i=" << i << endl;
}
};
class Derived:public Base{
public:
Derived(){}
Derived(bool arg) :Base(arg)
{
foo();
}
virtual void foo(int i = 12)
{
cout << "Derived:i=" << i << endl;
}
};
int main()
{
Base*b = new Derived(true);
b->foo();
cin.get();
return 0;
}
问题分析
1.构造函数和析构函数中不能调用虚函数
众所周知,在C++中多态(基类指针指向子类对象)是通过指针或者引用来实现的。在上述代码中Base*b = new Derived(true);正是C++多态的体现。
另外单从继承体系来说,对象的构造顺序是先构造父对象再构造子对象,而析构刚好相反。
在这里,父类Base中包含一个虚函数,在调用该构造函数时无法表现出多态特性。原因如下
(1)在父类构造阶段子类成员变量是未初始化的,此时如下降至子类,去调用尚未初始化的局部变量是一种危险的行为(未初始化的局部变量值为随机值),所以C++明确不让你走这条路。
(2)在父类构造阶段,只有父类对象,因此编译器会将虚函数解析至父类,此时若有运行时类型信息如dynaminc_cast或typeid,也会被视为父类类型。
析构函数原因类似,子类析构完成后,其成员变量都将呈现未定义值的行为。
需要注意的是:间接调用虚函数也是不被允许的。
2.默认参数是在编译期确定的
首先需要明确一点:默认参数值是静态绑定的,属于静态类型,而虚函数是动态绑定的,属于动态类型。静态类型是指声明时所采用的的类型;
而动态类型是在运行期所指对象的类型。
如Base*b = new Derived(true);所示,b的静态类型为Base*,而动态类型为Derived*。因此在调用b->foo()时,会输出42(父类函数中的默认参数)因为他由静态类型决定。
原因如下:
出现这种方式,主要原因是运行期效率。如果缺省参数值是动态绑定的,编译器就必须有某种方法在运行期为虚函数决定适当的参数默认值。这比目前实行的“”编译期决定”的机制更慢,而且更复杂。因此为了程序执行速度和编译器实现的简易度,C++做了这样的取舍。