首先明确两点

  1. 构造函数不可能定义为虚函数
    虚函数的目的是通过父类引用或者指针调用子类的成员函数。而构造函数的目的是创建对象。创建子类对象时,将调用子类的构造函数,而不是父类的构造函数。子类的构造函数将使用父类的一个构造函数。这种顺序不同于继承机制。因此,子类不继承父类的构造函数,所以将类的构造函数声明为虚函数没有意义。这个如果不能理解,记住就好了。
  2. 父类的析构函数应该是虚函数

关于第二点,举个例子
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指向的内存。