继承和多态二:虚析构函数

虽然我们已经知道了什么是继承和多态,也明白了多态依赖于继承,但是在多态中存在哪些问题呢?

多态中可能存在的内存泄露问题

例如下面的程序中,在圆形Circle的类中定义一个圆心的坐标,并且坐标是在堆中申请的内存,则在mian函数中通过基类指针操作派生类对象的成员函数是没有问题的,可是在销毁对象内存的时候则只是执行了基类的析构函数,派生类的析构函数却没有执行,这会导致内存泄漏。换句话说如果delete后边跟基类的指针则只会执行基类的析构函数,如果delete后面跟的是派生类的指针,那么它即会执行派生类的析构函数,也会执行基类的析构函数

class Shape
{
public:
    virtual double calcArea(){...}//虚函数
    Shape();
    ~Shape();
    ....                              
private:
    ....
};
Shape::Shape()
{
cout<<"class Shape was created"<<endl;
}
Shape::~Shape()
{
cout<<"class Shape was deleted"<<endl;
}
class Circle:public Shape
{
public:
    Circle(int x,int y,double r);
    ~Circle();
    virtual double calcArea();//此处可不加virtual,但系统默认加上
    ....
private:
    double m_dR;
    Coordinate *m_pCenter;      //坐标类指针
    ....
};
Circle::Circle(int x,int y,double r)
{
    m_pCenter=new Coordinate(x,y);
    m_dR=r;
    cout<<"class Circle was created"<<endl;
}
Circle::~Circle()
{
    delete m_pCenter;
    m_pCenter=NULL;
    cout<<"class Circle was deleted"<<endl;
}
....
int main()
{
    Shape *shape1=new Circle(5,6,4.0);//基类对象,改成Circle *circle=new Circle(5,6,4.0)则为派生类对象
    shape1->calcArea();
    delete shape1;
    shape1=NULL;//派生类对象的内存未回收
    return 0;
}
打印结果可见,派生类的析构函数并没有被执行,而只是回收了开辟给基类对象的空间,那如何解决这个问题呢,固然可以通过delete派生类对象实现,除此之外,还可以引入虚析构函数,你看普通的虚函数都可以实现多态的动态联编,那对于析构函数呢?虚析构函数也会在最后对象销毁内存时动态链接到派生类对象。
可以这样解释它:如果基类当中定义了虚析构函数,那么基类的虚函数表当中就会有一个基类的虚析构函数的入口指针,指向的是基类的虚析构函数,派生类的虚函数表当中也会产生一个派生类的虚析构函数的入口指针,指向的是派生类的虚析构函数,这个时候使用基类的指针指向派生类的对象,delete掉基类指针,就会通过指向的基类的对象找到基类的虚函数表指针,从而找到虚函数表,在虚函数表中找到派生类的虚析构函数,从而使得派生类的析构函数得以执行,派生类的析构函数执行之后系统会自动执行父类的虚析构函数。即整个执行过程是:
  1. 基类的构造函数;
  2. 派生类的构造函数;
  3. ......
  4. 派生类的析构函数;
  5. 基类的析构函数。

virtual关键字可以修饰普通的成员函数,也可以修饰析构函数,但并不是没有限制

virtual在函数中的使用限制

    • 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
    • 静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
    • 内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。(inline将会在后面的博客中介绍)
    • 构造函数不能是虚函数,否则会出现编译错误。
posted on 2018-03-02 11:22  greathuman  阅读(433)  评论(0编辑  收藏  举报