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

时间:2023-12-09 20:31:07

引言:

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

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 ;
}

但是实际运行就会发现: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 ;
}

调试结果在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", );
string *p;
p = d;
delete p; // 此时释放p,并不会调用~D(),因此原本给length赋值了5并不会被清除!!! return ;
}

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

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

 #include <iostream>
#include<string> using namespace std; class A {
public:
A() {};
virtual ~A() = ;
};
A::~A() {
cout << "~A" << endl;
}
class B : public A {
public:
B() {};
~B() {
cout << "~B" << endl;
}
}; int main()
{
B* b = new B();
delete b; return ;
}

运行后输出:

~B
~A

■总结:

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

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