C++之虚析构函数

本文介绍了C++虚析构函数,它是将析构函数声明为virtual。其作用是正常销毁多态模式下的派生类对象,防止内存泄漏。带有多态性质的基类应声明虚析构函数,若类不用于基类或无多态性则不应声明,否则会造成空间浪费。

定义

顾名思义,虚析构函数就是给析构函数声明为 virtual。

作用

虚析构函数可以正常的销毁多态模式下的派生类对象,防止造成一个诡异的“局部销毁”对象,从而防止形成内存泄漏。

使用场景

  • 带有多态性质的基类应该声明一个 virtual 析构函数。如果 class 带有任何 virtual 函数,它就应该拥有一个 virtual 析构函数。
  • 类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明为 virtual 析构函数。

虚析构函数

OK,上文已经总结了关于 virtual 析构函数的有关内容,接下来咱们仔细分析virtual 析构函数的用途以及到底为啥要声明 virtual 析构函数。

先来看一个示例:

#include <iostream>

using namespace std;

class Shape
{
public:
    Shape() {}
    ~Shape(){}
    virtual void draw() = 0;
};

class Rectange : public Shape
{
public:
    Rectange() {}
    ~Rectange(){}
    void draw(){
        //do something
        cout << "draw rectange.." << endl;
    }
};
class Circle : public Shape
{
public:
    Circle() {}
    ~Circle(){}
    void draw(){
        //do something
        cout << "draw Circle.." << endl;
    }
};
class Triangle : public Shape
{
public:
    Triangle() {}
    ~Triangle(){}
    void draw(){
        //do something
        cout << "draw Triangle.." << endl;
    }
};

int main()
{
    Shape * pRectange = new Rectange();
    Shape * pCircle = new Circle();
    Shape * pTriangle = new Triangle();

    pRectange->draw();
    pCircle->draw();
    pTriangle->draw();

    delete pRectange;
    delete pCircle;
    delete pTriangle;

    return 0;
}

首先,我们定义了一个图形基类Shape,并声明一个绘图接口 draw(),留给派生类去实现,然后分别有三个派生类继承于 Shape,然后在初始化对象的时候,动态创建派生类对象并赋给基类指针,通过基类指针对象调用绘图接口 draw()可以自动调用每个子类中的实现。这是一个最简单的多态结构。

那么问题就来了,动作执行完过后,我们通过 delete 删除基类指针,由于基类中的析构函数并不是虚析构函数,这就会引来一个灾难性问题。

C++明确指出,当派生类对象经由一个基类指针被删除,而该基类带着一个non-virtual析构函数(非虚析构函数),其结果未有定义-----实际执行时通常发生的是对象的派生成分没被销毁。

也就是说,当执行delete pRectange;时,Rectange内的成员变量很有可能没有被销毁,并且Rectange的析构函数也没有执行。
我们将上述代码中添加析构函数中的打印,如下:

~Rectange(){
        cout << "this is ~Rectange()" << endl;
    }

执行程序发现,执行delete pRectange;时并没有打印该析构函数中的输出。

然而基类成分通常会被销毁,于是造成一个诡异的“局部销毁对象”。这将导致资源泄露。

正确的做法就是将基类析构函数声明为 virtual,如下:

#include <iostream>

using namespace std;

class Shape
{
public:
    Shape() {}
    virtual ~Shape(){
        cout << "this is ~Shape()" << endl;
    }
    virtual void draw() = 0;
};

class Rectange : public Shape
{
public:
    Rectange() {}
    ~Rectange(){
        cout << "this is ~Rectange()" << endl;
    }
    void draw(){
        //do something
        cout << "draw rectange.." << endl;
    }
};
class Circle : public Shape
{
public:
    Circle() {}
    ~Circle(){
        cout << "this is ~Circle()" << endl;
    }
    void draw(){
        //do something
        cout << "draw Circle.." << endl;
    }
};
class Triangle : public Shape
{
public:
    Triangle() {}
    ~Triangle(){
        cout << "this is ~Triangle()" << endl;
    }
    void draw(){
        //do something
        cout << "draw Triangle.." << endl;
    }
};

int main()
{
    Shape * pRectange = new Rectange();
    Shape * pCircle = new Circle();
    Shape * pTriangle = new Triangle();

    pRectange->draw();
    pCircle->draw();
    pTriangle->draw();

    delete pRectange;
    pRectange = 0;
    delete pCircle;
    pCircle = 0;
    delete pTriangle;
    pTriangle = 0;

    return 0;
}

输出如下:

draw rectange..
draw Circle..
draw Triangle..
this is ~Rectange()
this is ~Shape()
this is ~Circle()
this is ~Shape()
this is ~Triangle()
this is ~Shape()

所以,任何 class 只要带有 virtual 函数都几乎确定应该也有一个 virtual 析构函数。

不要误用虚析构函数

刚刚我们看到,任何 class 只要带有 virtual 函数都几乎确定应该也有一个 virtual 析构函数。那么如果 class 不含 virtual 函数,通常表示它并不意图被用做一个 base class。当class 不企图被当做基类,让析构函数声明为 virtual 往往是个馊主意。

想要实现虚函数,对象必须携带某些信息,主要用来在运行期决定哪一个 virtual 函数应该被调用。这份信息通畅是由一个所谓的 vptr(virtual table pointer)指针指出。vptr 指向一个函数指针构成的数组,称为 vtbl(virtual table),每一个带有 virtual 函数的 class 都有一个对应的 vtbl,当对象调用某一个 virtual 函数,实际被调用的函数取决于该对象的 vptr 所指的那个 vtbl----编译器在其中寻找适当的函数指针。

所以说,如果给一个没有虚函数的基类声明析构函数为虚析构函数,其对象体积会增加,导致空间浪费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luoyayun361

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值