首先明确两点
- 构造函数不可能定义为虚函数
虚函数的目的是通过父类引用或者指针调用子类的成员函数。而构造函数的目的是创建对象。创建子类对象时,将调用子类的构造函数,而不是父类的构造函数。子类的构造函数将使用父类的一个构造函数。这种顺序不同于继承机制。因此,子类不继承父类的构造函数,所以将类的构造函数声明为虚函数没有意义。这个如果不能理解,记住就好了。 - 父类的析构函数应该是虚函数
关于第二点,举个例子
A是父类,B是A的子类,在B中还添加了一个char* name成员,name指向一块由new申请的内存。当B类的对象被销毁是,必然会调用~B()析构函数来释放内存。请看下面代码:
A* a = new B;
...
delete a; //此时调用哪个析构函数,~A()还是~B();
如果,A 的析构函数没有声明为虚函数,那么delete a使用默认的静态联编,也就是调用A的析构函数~A();这将会释放掉B类中A部分指向的内存,但是不会释放新的类成员指向的内存,也就是B的name成员指向的内存块,这样就造成内存泄漏的问题。但是如果A的析构函数声明为虚函数,那么B的虚函数表中会把指向A的析构函数的指针位置改为指向B的析构函数。此时调用delete a就会调用B的析构函数,释放掉由B指向的内存,然后调用A的析构函数,释放由A指向的内存。
空口无凭,来个例子看看再说。
例1
#include<iostream>
using namespace std;
class A {
public:
~A() {
cout<<"A::~A()"<<endl;
}
};
class B :public A{
private:
char* name;
public:
B() {
name = new char[5];
name[0] = '\0';
}
~B() {
cout<<"B::~B()"<<endl;
delete [] name;
}
};
int main()
{
A* a = new B();
delete a;
return 0;
}
这段代码执行结果:
A::~A()
可见仅仅执行了A的析构函数,释放掉了A指向的内存,但是B指向的内存没有被释放掉。
我们把A的析构函数声明为虚函数
virtual ~A() {
cout<<"A::~A()"<<endl;
}
再次执行,结果如下:
B::~B()
A::~A()
程序先调用子类的析构函数,释放掉B指向的内存,然后调用父类的析构函数,释放掉A指向的内存。