代码改变世界

白话C++系列(23) -- 虚析构函数

2016-06-06 21:24  Keiven_LY  阅读(1038)  评论(0编辑  收藏  举报

虚析构函数

之前我们已经重点学习了动态多态,那么在动态多态中还存在着问题----内存泄漏。那么,怎么来解决多态中的内存泄漏问题呢?我们来通过一个例子来说明。

在这里我们定义了一个Shape的类,又定义了一个Circle的类,并且Circle类以public的方式继承Shape类。但是这个Circle类与我们在讲动态多态的时候讲到的略有不同。(如下所示)

最大的不同就是,多定义了一个数据成员,这个数据成员是一个坐标类(COordinate)的指针,这个指针代表的意思就是这个圆的圆心坐标,我们会在它的构造函数当中去实例化一个坐标对象,并且把这个坐标对象作为圆心坐标,并且适用m_pCenter去指向这个Coordinate对象。指向之后,我们会在析构函数执行的时候把这个对象再释放掉,这样就能够保证在实例化Circle后去使用它,使用完成之后还能将指针在堆中的内存释放掉,从而保证内存步泄漏。

可是在多态的使用当中,我们可以看一下:如果用父类指针去指向子类对象,并且通过父类指针去操作子类对象当中相应的虚函数,这个时候是没有问题的(这个前面已经讲过)。

可是后面的部分是有问题的,当我们使用delete去销毁对象,并且是借助于父类的指针想去销毁子类对象的时候,这个时候就出现问题了,为什么呢?我们在前面的课程给大家继承篇的时候,大家应该还记得:如果delete后边跟的是一个父类的指针,那么它只会执行父类的析构函数;如果跟着的是一个子类的指针,那么它既会执行子类的析构函数,也会执行父类的析构函数可见,如果我们delete后面跟的是父类的指针(如上 *shape1),就只执行了父类的析构函数,又怎么能执行到Circle的析构函数呢?执行不到Circle的析构函数,那岂不是就造成了内存的泄漏了吗?因为我们在实例化Circle的对象的时候,是从堆中去申请的一段内存,并且把这段内存作为它的圆心坐标。从而可见,我们必须要解决这个问题,不解决的话,每次去使用的时候都会造成内存的泄漏问题。

讲到这里,我们可能会问:那之前的课程讲到这里,可没有说道有内存泄漏啊?那是有原因的!!!我们之前在给大家讲Circle这个类的时候,在Circle这个类当中一是没有指针型的数据成员(*m_pCenter),二是没有在构造函数当中去申请内存因为这两个原因,在这种情况下,它的构造函数当中其实什么也不做,既然什么也不做,那么我们执行它和步执行它区别就不大了,所以当时不会产生内存泄漏问题。但是,现在这种情况不一样了,那对于现在这种情况,我们如何来解决呢??我们必须要引入虚析构函数这个概念。

那么,什么是虚析构函数呢?

我们用关键字virtual去修饰析构函数,称此时的析构函数就叫做虚析构函数。从写法上来讲,也很简单,如下:

当我们用这种方式修饰了Shape的析构函数之后,那么Shape的子类,在这里就是Circle类中的析构函数前面既可以写上关键字virtual,也可以不写关键字virtual,如果不写,系统在编译时会自动加上。不过这里还是推荐大家写上,这样,将来再有类继承Circler的时候,也就知道Circle的析构函数是带有virtual的,那么它的子类的析构函数也就应该带有virtual了。当我们定义完成了虚析构函数之后呢我们在main函数当中,就可以适用之前的方式进行相应的操作了。这个时候我们再适用delete,如果此时在delete后面跟上父类指针的时候,那么父类指针指向的是哪个对象,那么这个对象的析构函数就会先可以执行,然后再执行它父类的析构函数,于是可以保证内存不被泄漏。

问题:关键字virtual既可以修饰普通的成员函数,也可以修饰析构函数,那它是不是就没有什么限制呢?

Virtual的使用限制

1) Virtual不能修饰普通的函数,必须是某个类的成员函数

2) Virtual不能修饰静态成员函数

如果用virtual去修饰一个静态成员函数的话,它不属于任何一个对象,它是和类是同生共死的,所以当用virtual去修饰的时候,也会造成编译错误

3) Virtual不能修饰内联函数

如果用viryual去修饰内联函数,那么,对于计算机来说,它会忽略掉inline关键字,而使它变成一个纯粹的虚函数。

4) Virtual不能修饰构造函数

虚析构函数代码实践

题目描述:

/*   **************************************************  */

/* 虚析构函数 */

/*   要求:

       1. 定义Shape类,成员函数:calcArea(),构造函数,析构函数

       2. 定义Rect类,成员函数:calcArea(),构造函数,析构函数

                      数据成员:m_dWidth,m_dHeight

       3. 定义Circle类,成员函数:calcArea(),构造函数,析构函数

                         数据成员:m_dR, m_pCenter

       4. 定义Coordinate类,成员函数:构造函数,析构函数

                            数据成员:m_iX,m_iY

/*   **************************************************  */

程序框架:

 

头文件(Shape.h

#ifndef SHAPE_H
#define SHAPE_H

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape();
    ~Shape();
    virtual double calcArea();
};

#endif

源程序(Shape.cpp

#include "Shape.h"
#include <iostream>
using namespace std;

Shape::Shape()
{
    cout << "Shape()" << endl;
}

Shape::~Shape()
{
    cout << "~Shape()" << endl;
}

double Shape::calcArea()
{
    cout << "Shape---calcArea()" << endl;
    return 0;
}

头文件(Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"
#include "Coordinate.h" //为了使用这个类来定义一个指针,所以要包含进来

class Circle:public Shape
{
public:
    Circle(double r);
    ~Circle();
    virtual double calcArea();
protected:
    double m_dR;
    Coordinate *m_pCenter; //申明一个圆点类型的指针
};

#endif

源程序(Circle.cpp

#include "Circle.h"
#include <iostream>
using namespace std;

Circle::Circle(double r)
{
    cout << "Circle()" << endl;
    m_dR = r;
    m_pCenter = new Coordinate(3, 5); //实例化一个Coordinate对象作为圆心,并将m_pCenter指向这段内存
}
Circle::~Circle()
{
    cout << "~Circle()" << endl;
    delete m_pCenter;
    m_pCenter = NULL;

}
double Circle::calcArea()
{
    cout << "Circle----calcArea()" << endl;
    return 3.14*m_dR*m_dR;
}

头文件(Rect.h

#ifndef RECT_H
#define RECT_H

#include "Shape.h"

class Rect:public Shape
{
public:
    Rect(double width, double height);
    ~Rect();
    virtual double calcArea();
protected:
    double m_dWidth;
    double m_dHeight;
};

#endif

源程序(Rect.cpp

#include "Rect.h"

#include <iostream>
using namespace std;

Rect::Rect(double width, double height)
{
    cout << "Rect()" << endl;
    m_dWidth = width;
    m_dHeight = height;
}
Rect::~Rect()
{
    cout << "~Rect()" << endl;
}
double Rect::calcArea()
{
    cout << "Rect----calcArea()" << endl;
    return m_dWidth * m_dHeight;
}

头文件(Coordinate.h

#ifndef COORDINATE_H
#define COORDINATE_H

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
private:
    int m_iX;
    int m_iY;
};

#endif

源程序(Coordinate.cpp

 #include "Coordinate.h"
#include <iostream>
using namespace std;

Coordinate::Coordinate(int x, int y)
{
    cout <<"Coordinate()" << endl;
    m_iX = x;
    m_iY = y;
}
Coordinate::~Coordinate()
{
    cout <<"~Coordinate()" << endl;
}

主调程序(demo.cpp

在这里我们还是先引用上一节课的demo.cpp,先不作任何修改如下:

#include <iostream>
#include <stdlib.h>
#include "Circle.h"
#include "Rect.h"
using namespace std;


int main()
{
    Shape *shape1 = new Rect(3, 6); //传入宽和高
    Shape *shape2 = new Circle(5);  //传入半径 
    shape1 ->calcArea();
    shape2 ->calcArea();

    delete shape1;
    shape1 = NULL;
    delete shape2;
    shape2 = NULL;

    system("pause");
    return 0;
}

直接运行看结果:

我们此次要看的重点是构造函数和析构函数。首先,我们先去实例化Rect的时候,执行了Shape和Rect这两个构造函数;去实例化Circle的时候,执行了Shape和Circle这两个构造函数,;然后执行了Coordinate构造函数,这是因为我们在实例化Circle的时候,在Circle的构造函数当中,我们去实例化了一个Coordinate对象,这才使得Coordinate构造函数得以执行。但是,请大家注意:在后边所有打印出的内容当中,并没有去执行Circle的析构函数,这就意味着,在Circle的析构函数当中,去释放对象的过程没有得到执行。换句话说,就造成了内存的泄漏,泄露的是什么呢?泄漏的就是Coordinate这个对象。我们怎样才能够保证内存不被泄漏呢?我们需要加上关键字virtual,给谁加呢?给Shape的析构函数加就可以了,使Shape的析构函数变成一个虚析构函数,,于是继承Shape的其他类,比如Rect和Circle这两个类的析构函数也变成了虚析构函数,在这里我们最好将这两个子类的析构函数前面也加上virtual,看看现在的执行效果:

与之前的结果进行比较,主要看下半部分。当我们去销毁Shape1的时候,先执行的是Rect的析构函数,又执行了Shape的析构函数;当我们去销毁Shape2的时候,先执行的是Circle的析构函数,又执行了Coordinate的析构函数,最后执行的是Shape的析构函数。