虚析构函数

来源:互联网 发布:php webservice 编辑:程序博客网 时间:2024/06/08 14:53

析构函数

C++语言中,每一个类都有一个默认的或者我们自定义的析构函数,析构函数的作用就是当对象销毁的时候做一些资源释放,一般我们都是这样用的。然而,有时候可能由于疏忽或者对语法不熟悉,写出来的程序最终当对象销毁的时候,析构函数并没有被调用。这种情况是存在的,下面我们举一个列子:

class Base{public:    Base();    ~Base();       int member;};class Derived : public Base{public:    Derived();    ~Derived();};Base* base = new Derived;delete base;

这是一个简单的继承关系,我们new了一个派生类对象,然后把该对象地址给了基类,最终我们通过这个基类指针删除了我们刚才new的派生类对象指针。这样的操作在我们实际开发中很正常,然而这里的派生类对象的析构函数并没有被调用。如果说本来我们在派生类的析构函数里面做了一些释放内存的操作,可是我们这样的写法,派生类的析构函数永远都不会被调用,这样的代码显然存在内存泄漏的问题。
为什么派生类的析构函数没有被调用,原因是我们delete的是基类指针,所以当调用析构函数的时候只能调用基类的指针,这个是由于类型造成的,基类类型他当然调用的是基类的析构函数。

虚析构函数

当我们将基类的析构函数声明成虚函数的时候,上面我们所存在派生类析构函数不被调用的问题就没有了。也就是说当我们把基类的析构函数声明成虚函数的时候,就会调用派生类的虚函数。

class Base{public:    Base();    virtual ~Base();    int member;};class Derived : public Base{public:    Derived();    ~Derived();};Base* base = new Derived;delete base;// 当基类的析构函数为虚函数时,后面的调用顺序如下:// Derived::~Derived();// Base::~Base();

如上代码所示,当delete时,会先调用派生类的析构函数,然后再调用基类的析构函数。会调用派生类的析构函数是我们将基类析构函数声明成虚函数的原因;这里还有一个调完派生类析构会再调用基类析构,这就像初始化一个派生类对象一样,调用派生类构造函数的时候必然是先调用基类构造函数一样的道理,而析构和构造是相反的。所以当派生类析构被调用的时候,基类的析构也被调用了。
所以,为什么我们在定义基类的析构函数的时候要将其声明成虚函数。很明显,这个问题的答案就是我们上面的内容所要表达的。

虚析构函数的作用和原理

通过上面我们应该也能明白虚析构函数的作用就是,当我们用基类的指针来销毁一个派生类对象的时候保证派生类对象的析构函数被调用,防止内存泄漏的问题。
下面我们重点说一下他的原理,为什么将基类的析构函数声明成虚函数就能使得delete一个指向派生类对象的基类指针的时候调用派生类对象的析构函数,而非只调用基类对象的析构函数。
首先我们应该明白一件事情,一个有虚函数和没有虚函数的类的对象的内存布局是不同的,当有虚函数的时候,在对象构造的时候就会比没有虚函数的对象多一个虚函数表指针,这个虚函数表指针指向一个虚函数表,这个虚函数表里面保存着所有的虚函数指针,如下图所示:
这里写图片描述

当把基类的析构函数声明成虚函数的时候,对象内部就会多一个虚函数表指针,此时,当对象销毁要调用析构函数的时候就会去虚函数表里面找析构函数,那么假设有如下的调用方式:

Base* base = new Derived;delete base;//当delete之后,就会以如下方式调用析构函数://(*base->vptr[1])(base) <=> Derived::~Derived();//接着再调用基类的析构函数//Base::~Base();

由于前面Derived对象地址给base的时候只是地址的传递,所以,base所持有的虚函数表是Derived的虚函数表,所以*base->vptr[1]对应的是Derived::~Derived(),先调用Derived::~Derived(),再调用Base::~Base(),这样便没有问题了。
假如不将Base类的析构函数声明成虚函数,当delete base的时候,由于此时base的类型是Base类型,所以就会调用Base::~Base(),导致Derived::~Derived()不被调用。

总结和思考

将基类的析构函数声明为虚函数就可以避免当delete一个指向派生类对象的基类指针的时候,派生类对象的析构函数不被调用的问题。这种情况在我们平时是经常容易遇到的,所以如果平时写代码的时候能有记得这样做,养成一个良好的代码习惯,就能够有效避免可能出现的bug。

这里发现一个问题,虚析构函数的这种处理方式是不是多态?看起来形式上是一样的,但却有不同之处,首先多态发生的条件是:

  1. 继承关系
  2. 重写虚函数
  3. 指向派生类的基类类型的指针或引用调用被重写的虚函数

这里我们的虚析构函数并不满足前两个,第三个条件勉强满足。所以我认为这并不是严格意义上的多态。只是由于析构函数的特殊性,当它被声明成虚函数的时候,它在虚函数表中的位置和一般多态中普通虚函数满足多态条件时一样,这样就表现出了和多态相似的效果。当然不完全相同,因为它没有重写,并且派生类的析构函数执行完后还会再执行基类析构函数。

0 0
原创粉丝点击