为什么构造函数不能是虚函数而析构函数可以

首先,虚函数的实现原理是:在定义具有虚函数的类或者继承类的继承的时候,会相应建立一个虚函数表vtable,即每个类都对应一个需函数表,而在定义类的对象的时候,每个对象都会有一个指向相应类的虚表指针vptr,vptr指向虚表的入口地址,在调用相应的虚函数的时候,根据该入口地址寻找对应的函数。
对于构造函数,其作用是在对象实例化的时候自动调用,对该对象进行初始化操作。前述中提到,虚函数是通过vptr来调用的,而调用构造函数的时候实例化并未完成,也就是说此时并不存在vptr,因而,无法使用vptr来调用构造函数。
另一方面,虚函数的调用是虚调用,通过在运行时查询虚函数表得到具体函数入口地址,相当于只需要有部分信息就可以调用该函数。然而定义具体类的对象的时候,需要明确指定对象类型,而且在定义子类对象的时候首先调用的是父类的构造函数然后才是调用子类构造函数,如果使用了虚函数,那么仅仅是调用子类构造函数并不能完成对象的初始化。
而对于析构函数,则需要定义为虚析构函数,防止内存泄露的发生。
如下代码:
#include <iostream>

class A{
    public: 
            A(){
                std::cout<<"construct A!"<<std::endl;
            };
            ~A(){
                 std::cout<<"A has been destructed!"<<std::endl;
            }   
};
class B:public A{
    public:
           B(){
               std::cout<<"construct B!"<<std::endl;
           };
           ~B(){
                std::cout<<"B has been destructed!"<<std::endl; 
           } 
};
int main()
{
    B* ptrB=new B;
    delete ptrB;
}
此时的输出为:
construct A
construct B
B has been destructed!
A has been destructed!

也就是对于普通的定义子类对象然后析构该对象的行为,其首先是调用子类的析构函数,然后再调用父类的析构函数,因为在定义子类对象的时候,首先是调用了父类对象的构造函数,然后才是调用子类的构造函数,那么子类对象中包含了父类的信息,析构的时候也必然需要调用父类的析构函数。
而如果使用父类的指针指向子类的对象:

int main()
{
    A* ptrAB=new B;
    delete ptrAB;
}
此时的输出是
construct A
construct B
A has been constructe!

此时仅仅是调用了父类的析构函数,因为指针类型是父类的。此时会造成的后果是,保存子类信息的那部分内存空间没有被析构,导致内存泄露。
如果将析构函数定义成虚函数,那么输出的时候则和普通定义的子类的对象析构一样,输出为:

construct A
construct B
B has been destructed!
A has been destructed!

此时的析构行为才是正确的,即先调用子类析构函数,再调用父类析构函数。

你可能感兴趣的:(虚函数,构造函数)