标题不要在构造函数和析构函数中执行虚函数
今天的主题我用一句话总结:如果你在基类的构造函数或者析构函数中调用虚函数,那么这个虚函数会被当做普通的成员函数。
1.对于构造函数:
子类在执行构造函数之前会先执行父类的构造函数,此时子类的专属部分还未被初始化,所以面对那些专属部分编译器就把它视而不见,也就是子类在构造函数之前编译器将子类(派生类)当做成了父类(基类)。
2.对于析构函数:
析构函数的执行顺序是先执行派生类的析构函数然后再执行基类的析构函数。当执行基类析构函数的时候,派生类的析构函数已经执行完毕,所以此时编译器就把派生类的成员当做未定义,也就是说当进入基类的析构函数中时,将派生类当做一个基类对象。
接下来看个例子:
class A{
public:
A()
{
cout << "A:print()*****:";
print();
}
virtual void print()
{
cout << "I am A\n";
}
~A()
{
cout << "~A:print()*****:";
print();
}
};
class B:public A{
public:
B()
{
cout << "B:print()*****:";
print();
}
virtual void print()
{
cout << "I am B\n";
}
~B()
{
cout << "~B:print()*****:";
print();
}
};
void playxx()
{
B b;
}
int main()
{
playxx();
system("pause");
}
结果:
从结果可以看出来,在基类执行析构函数的时候,只调用了基类的虚函数,并没有调用子类的析构函数。其实总结下来也就是:在派生类对象的基类部分构造期间,就把派生类对象视为一个基类对象,这时调用的虚函数版本,就是基类的版本。
构造函数的调用顺序是从基类到派生类,逐层构造。在构造的过程中,vptr被指向本层的vtable。而虚函数的行为依赖于vptr。因此,在本层构造函数中,编译器无法获知派生类的任何信息,因此无法形成正确的vtable访问。Vtable在上节讲过,它是存储在一段连续的内存空间中,因为派生类构造函数还没有执行,所有没有空间,从而也就没有生成vtable.