白话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的析构函数。