[Effective C++ --007]为多态基类声明virtual析构函数

引言:

    我们都知道类的一个很明显的特性是多态,比如我们声明一个水果的基类:

class Fruit {
public:
    Fruit() {};
    ~Fruit(){};
}

   那么我们根据这个Fruit基类可以派生出以下的子类:

   class Apple:public Fruit{};
   class Orange:public Fruit{};

那么问题来了,如果我们想经由一个基类指针去删除一个派生类Apple,且我们有以下的方法

Fruit * foo(){
       Apple * p = new Apple();
       return p; // 子转父,大丈夫
}

接下来似乎顺理成章,我们只需要完成以下的main函数即可了。

int main() {
        A *p = foo();
        delete p;
        return 0;
}

 但是实际运行就会发现:Apple对象并未被删除!

这是为什么呢?因为foo()返回的指针指向一个Apple对象,其中的Apple的成员变量很可能未被销毁,而且Apple的析构函数也未被执行起来。于是造成了一个局部销毁的局面。

一、解决方案

其实很简单,只需要将基类中的析构函数定义为虚函数即可。

class Fruit {
public:
    Fruit() {};
    virtual ~Fruit(){};   ←
}

这样在删除p的时候,会先调用Apple的析构函数,然后再调用Fruit的析构函数,最后删除整个Apple对象。

二、扩展

  在书中所说:

   在string等STL容器使用的时候,即时class不带virtual函数,可能还是会被是否为虚函数的问题折腾。比如我们拿string来作为一个基类声明以下的class

#include <iostream>
#include<string>
using namespace std;

class D : public string
{
public:
    D(string i) {};
    ~D() {};
    string i;
};

int main()
{
    D* d = new D("DD");
    string *p;
    p = d;
    delete p;     // 书中描写此处会发生内存泄露,但是十分不解,上面的p =d 是子赋给父,按理是不应该出现问题的

    return 0;
}

调试结果在VS环境下也不会出现错误,不清楚是为什么,还有待调查。

2014.11.11 调查结果:

在上述结果中,确实不会出现问题,因为只是单纯的删除了p,但是d中间的值有没有得到删除就不能得到确定了。

这跟我们删除d的目的是相悖的。

添加以下的代码来做说明:

class D : public string
{
public:
    D(string str, int i): s(str), length(i){};
    ~D() {
        cout << "call me";
    };
private:
    string s;
    int length;
};

int main()
{
    D* d = new D("DD", 5);
    string *p;
    p = d;
    delete p;  // 此时释放p,并不会调用~D(),因此原本给length赋值了5并不会被清除!!!

    return 0;
}    

 三、纯虚析构函数的调用顺序

在基类存在纯虚析构函数的时候,析构函数的运作方式是:派生class的析构函数先被调用,然后再调用基类的析构函数。

 1 #include <iostream>
 2 #include<string>
 3 
 4 using namespace std;
 5 
 6 class A {
 7 public:
 8     A() {};
 9     virtual ~A() = 0;
10 };
11 A::~A() {
12     cout << "~A" << endl;
13 }
14 class B : public A {
15 public:
16     B() {};
17     ~B() {
18         cout << "~B" << endl;
19     }
20 };
21 
22 int main()
23 {
24     B* b = new B();
25     delete b;
26 
27     return 0;
28 }

运行后输出: 

~B
~A

■总结:

1.带多态性质的基类应该声明一个virtual析构函数,如果class带有任何的虚函数,那么它就应该拥有一个virtual析构函数。

2.class的设计目的如果不是作为基类使用,或不是为了具备多态性,就不应该声明virtual析构函数。

posted @ 2014-11-10 18:20  依然冷月  阅读(216)  评论(0编辑  收藏  举报