构造函数、析构函数与虚函数的关系
1、为什么构造函数不能是虚函数?
因为:从使用上来说,虚函数是通过基类指针或引用来调用派生类的成员的,则在调用之前,对象必须存在,而构造函数是为了创建对象的。
2、为什么在派生类中的析构函数常常为虚析构函数
注意,默认不是析构函数
一句话,是为了避免内存泄露
如果不考虑虚函数的状况,给出一个基类和派生类,如果调用派生类的析构函数时,肯定会引发调用基类的析构函数,这和析构函数是不是虚函数没关系。
现在考虑虚函数的问题,由于使用虚函数使我们可以定义一个基类指针或引用可以直接对派生类进行操作,这就存在两种情况:
如果,不把基类的析构函数设置为虚函数,则在删除对象时,如果直接删除基类指针,系统就只能调用基类析构函数,而不会调用派生类构造函数。这就会导致内存泄露。
如果,把基类的析构函数设置为虚函数,则在删除对象时,直接删除基类指针,系统会调用派生类析构函数,之后此派生类析构函数会引发系统自动调用自己的基类,这就不会导致内存泄露。
所以,在写一个类时,尽量将其析构函数设置为虚函数,但析构函数默认不是虚函数。
举例一:通过派生类指针删除派生类对象的情况
- #include<iostream>
- using namespace std;
- class Base
- {
- public:
- ~Base()
- {
- cout<<" Base 的析构函数"<<endl;
- }
- };
- class Derive : public Base
- {
- public:
- ~Derive()
- {
- cout<<" Derive 的析构函数"<<endl;
- }
- };
- void main()
- {
- Derive* p = new Derive();
- delete p;
- system("pause");
- }
结果:
分析:即调用了基类的析构函数,又调用了派生类的析构函数
说明:
(1)p是指向派生类Derive的指针,删除p时,会自动调用Derive的析构函数,同时在之后,有继续向上调用基类Base的析构函数。
(2)这个过程和虚函数没有关系,只要调用派生类的析构函数,就自动回调用其祖先的析构函数。
举例二:通过基类指针删除派生类对象时 且 没有把基类的析构函数设置为虚函数的情况
- #include<iostream>
- using namespace std;
- class Base
- {
- public:
- ~Base()
- {
- cout<<" Base 的析构函数"<<endl;
- }
- };
- class Derive : public Base
- {
- public:
- ~Derive()
- {
- cout<<" Derive 的析构函数"<<endl;
- }
- };
- void main()
- {
- Base* p = new Derive();
- delete p;
- system("pause");
- }
结果:
分析:只调用了基类的析构函数,没调用了派生类的析构函数
说明:
(1)由于p是指向基类Base的指针,而且其Base的析构函数也不是虚函数,在删除p时,会直接调用Base的析构函数,而不会调用自己实际指向Derive的析构函数(这是用来和后面对比的),如果在Derive中的析构函数中有内存的释放,就会造成内存泄露。
举例三:通过基类指针删除派生类对象时 且 把基类的析构函数设置为虚函数的情况
- #include<iostream>
- using namespace std;
- class Base
- {
- public:
- virtual ~Base()
- {
- cout<<" Base 的析构函数"<<endl;
- }
- };
- class Derive : public Base
- {
- public:
- ~Derive()
- {
- cout<<" Derive 的析构函数"<<endl;
- }
- };
- void main()
- {
- Base* p = new Derive();
- delete p;
- system("pause");
- }
结果
分析
(1)由于p是指向派生类Base的指针,而且其析构函数是虚函数,由于虚函数的性质,在删除p时,会直接调用Derive的析构函数
(2)在调用Derive的析构函数后,会引发Derive的基类Base的析构函数的调用。这和其是否是虚函数没关系,只是析构函数自己的功能。
总结,在有派生存在的类集合中,基类的析构函数尽量设置为虚函数,而且常常为虚函数,但默认不是虚函数,而且不需要一定设置为虚函数。
注意:如果基类的析构函数设置为虚函数,那么所有的派生类也默认为虚析构函数,即使没有带关键字Virtual。
在一个基类中,析构函数设置为虚析构函数的原因是什么?
主要是因为:
(1)需要使用 指向基类指针对派生类进行操作时,才有可能会导致内存泄露。如果不需要这样操作,完全可以不这么设置。
(2)基类的析构函数设置为虚函数后,其派生出类的析构函数自动为虚函数。
3、把所有的类的析构函数都设置为虚函数好吗?
肯定不好,其实这就是想问虚函数的缺点。
虚函数属于在运行时进行处理的,为了在运行时根据不同的对象调用不同的虚函数,这就要求具有虚函数的类拥有一些额外信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。系统为每一个对象存储了一个虚函数表vtbl(virtual table)。虚函数表是一个函数指针数组,数组中每一个成员都包含一个虚函数,并把这个表的首地址存储在 vptr(virtual table pointer)中。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。
如图:
因此,使用虚函数后的类对象要比不使用虚函数的类对象占的空间多,而且在查找具体使用哪一个虚函数时,还会有时间代价。即当一个类不打算作为基类时,不用将其中的函数设置为虚函数。