从 C 向 C++ 进阶系列导航
1. 构造、析构与虚函数
可否想过,构造函数和析构虚函数可以声明为虚函数吗?答案是,构造函数不能声明为虚函数,而析构函数可以声明为虚函数。
虚函数表指针的初始化是发生在构造函数完成之后的,因此,把构造函数编译为虚函数是无意义的。编译时会直接报错。
当类作为父类时,应声明析构函数为虚函数。因为对于堆上的类对象,使用关键字 delete 释放堆空间时,假设父类的析构函数不是虚函数,此时只会调用接收对象的指针类型的析构函数,子类很可能没有被正常地析构;如果声明为虚函数,则可以先调用子类的析构函数再调用父类的析构函数,即为正确的析构顺序。
- 实验:
class Base_A
{
public:
~Base_A()
{
cout << "in Base_A" << endl;
}
};
class Base_B
{
public:
virtual ~Base_B()
{
cout << "in Base_B" << endl;
}
};
class Derived_A : public Base_A
{
public:
virtual ~Derived_A()
{
cout << "in Derived_A" << endl;
}
};
class Derived_B : public Base_B
{
public:
virtual ~Derived_B()
{
cout << "in Derived_B" << endl;
}
};
int main()
{
Base_A* p_A = new(Derived_A);
delete p_A; // in Base_A
Base_B* p_B = new(Derived_B);
delete p_B; // in Derived_B
// in Base_B
return 0;
}
2. 构造、析构中的多态行为
可否想过,构造函数和析构虚函数中是否可以发生多态行为呢?答案是,构造函数与析构函数都不可以发生多态行为。
由于虚函数表指针的初始化发生在构造函数完成之后,因此在构造函数中是无法构成多态行为的,所有想发生多态行为的函数调用都会直接调用本类的虚函数。
而对于析构函数,假设允许其存在多态行为,那么在调用父类的析构函数时,根据多态行为很可能会通过子类的成员函数访问到子类的成员变量,但此时子类应该是先于父类被析构了,因此这种行为是存在很大的风险的。因此,编译器在析构函数中发现调用到虚函数时,会强制转换直接调用本类的虚函数,即不再产生访问虚函数表的动作。
- 实验:
class Base
{
public:
Base()
{
Base* p = this;
p->Print();
}
virtual ~Base()
{
Base* p = this;
p->Print();
}
virtual void Print()
{
cout << "in Base" << endl;
}
};
class Derived : public Base
{
public:
virtual ~Derived()
{
Base* p = this;
p->Print();
}
virtual void Print()
{
cout << "in Derived" << endl;
}
};
int main()
{
Base* p = new(Derived); // int Base
delete p; // int Derived
// in Base
return 0;
}
以上实验中,析构函数的多态行为会被替换为直接的成员函数调用,即p->Print();
会直接替换为Print();
。