简单总结就是:
构造函数不可以是虚函数,而析构函数可以且常常是虚函数。
构造函数不能是虚函数
1. 从vptr角度解释
虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的vptr指针(vptr可以参考
C++的虚函数表指针vptr
)指向,该指针存放在对象的内部空间中,需要调用构造函数完成初始化。如果构造函数是虚函数,那么调用构造函数就需要去找vptr,但此时vptr还没有初始化!
2. 从多态角度解释
虚函数主要是实现多态,在运行时才可以明确调用对象,根据传入的对象类型来调用函数,例如通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用。那使用虚函数也没有实际意义。
在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,没有必要成为虚函数。
析构函数可以且常常是虚函数
这个原理上就很好理解啦,因为此时 vtable 已经初始化了,完全可以把析构函数放在虚函数表里面来调用。
C++类有继承时,析构函数必须为虚函数。
如果不是虚函数,则使用时可能存在
内存泄漏
的问题。
假设我们有这样一种继承关系:
如果我们以这种方式创建对象:
SubClass* pObj = new SubClass();
delete pObj;
不管析构函数是否是虚函数(即是否加virtual关键词),delete时基类和子类都会被释放;
如果我们以这种方式创建对象:
BaseClass* pObj = new SubClass();
delete pObj;
- 若析构函数是虚函数(即加上virtual关键词),delete时基类和子类都会被释放;
-
若析构函数不是虚函数(即不加virtual关键词),delete时只释放基类,不释放子类;
(1)基类的析构函数不是虚函数的话,删除指针时,只有基类的内存被释放,派生类的没有。这样就内存泄漏了。
(2)析构函数不是虚函数的话,直接按指针类型调用该类型的析构函数代码,因为指针类型是基类,所以直接调用基类析构函数代码。
(3)养成习惯:基类的析构一定virtual。
写代码验证一下
#include <iostream>
using namespace std;
class Father
{
public:
Father(){cout<<"contructor Father!"<<endl;};
~Father(){cout<<"destructor Father!"<<endl;};
};
class Son:public Father
{
public:
Son(){cout<<"contructor Son!"<<endl;};
~Son(){cout<<"destructor Son!"<<endl;};
};
int main()
{
Father *pfather=new Son;
delete pfather;
pfather=NULL;
return 0;
}
/*输出结果为:
contructor Father!
contructor Son!
destructor Father!
*/
可以看到,析构函数不是虚函数时,子类的析构函数并没有被调用!
改成虚析构函数后,就可以啦!