构造函数不能是虚函数,主要有两个原因:
1.派生类不能继承基类的构造函数,因此把基类的构造函数声明为虚函数没有意义,无法实现多态;
2.C++中的构造函数用来的在创建对象的时候进行初始化工作,在执行构造函数的时候,对象尚未创建完成,虚函数表这个时候还不存在,也没有指向虚函数表的指针,所以此时还无法查询虚函数表。也就不知道调用哪一个构造函数。
析构函数用来在销毁对象的时候进行清理工作,可以声明为虚函数,有时必须声明为虚函数。
#include<iostream> using namespace std; class Base{ public: Base(); ~Base(); protected: char *str; }; Base::Base(){ str=new char[100]; cout<<"Base constractor"<<endl; } Base::~Base(){ delete[] str; cout<<"Base deconstractor"<<endl; } class Derived:public Base{ public: Derived(); ~Derived(); private: char *name; }; Derived::Derived(){ name=new char[100]; cout<<"Dervied constractor"<<endl; } Derived::~Derived(){ delete[] name; cout<<"Derived deconstractor"<<endl; } int main() { Base *pb = new Derived(); delete pb; cout<<"-----------------------"<<endl; Derived *pd = new Derived(); delete pd; }
执行结果:
Base constractor Dervied constractor Base deconstractor ----------------------- Base constractor Dervied constractor Derived deconstractor Base deconstractor
从运行结果可以看出,语句delete pb;
只调用了基类的析构函数,没有调用派生类的析构函数;而语句delete pd;
同时调用了派生类和基类的析构函数。
在本例中,不调用派生类的析构函数会导致 name 指向的 100 个 char 类型的内存空间得不到释放;除非程序运行结束由操作系统回收,否则就再也没有机会释放这些内存。这是典型的内存泄露。
为什么delete pb不会调用派生类的析构函数呢?
因为这里的析构函数是非虚函数,通过指针访问非虚函数时候,编译器会根据指针是类型来调用析构函数。也就是说,指针是什么类型,就调用哪个类的析构函数。如这里pb是基类指针,就调用基类的析构函数。
为什么delete pd会同时调用基类和派生类的析构函数?
pd 是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数。
更改上面的代码,把析构函数更改虚函数:
#include<iostream> using namespace std; class Base{ public: Base(); virtual ~Base(); protected: char *str; }; Base::Base(){ str=new char[100]; cout<<"Base constractor"<<endl; } Base::~Base(){ delete[] str; cout<<"Base deconstractor"<<endl; } class Derived:public Base{ public: Derived(); ~Derived(); private: char *name; }; Derived::Derived(){ name=new char[100]; cout<<"Dervied constractor"<<endl; } Derived::~Derived(){ delete[] name; cout<<"Derived deconstractor"<<endl; } int main() { Base *pb = new Derived(); delete pb; cout<<"-----------------------"<<endl; Derived *pd = new Derived(); delete pd; }
执行结果:
Base constractor Dervied constractor Derived deconstractor Base deconstractor ----------------------- Base constractor Dervied constractor Derived deconstractor Base deconstractor
将基类的析构函数声明为虚函数后,派生类也会自动称为虚函数,这个时候,编译器会忽略指针类型,而是根据指针的指向来调用函数,也就是说,指针指向那个对象就调用那个对象的函数。
pb、pd 都指向了派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。如此一来也就解决了内存泄露的问题。
实际开发中,一旦我们自己定义了析构函数,就是希望在对象销毁时用它来进行清理工作,比如释放内存、关闭文件等,如果这个类又是一个基类,那么我们就必须将该析构函数声明为虚函数,否则就有内存泄露的风险。也就是说,大部分情况下都应该将基类的析构函数声明为虚函数。
注意,这里强调的是基类,如果一个类是最终的类,那就没必要再声明为虚函数了。