条款7:为多态基类声明virtual析构函数
C++中明确规定:为derived class对象经由base class指针删除时,而该base class指针带有一个non-virtual函数时,其结果未定义--实际执行时通常发生的是derived对象没有被销毁。
1、时钟例子
class TimeKeeper
{
public:
TimeKeeper()
{
cout<<"TimeKeeper construction\n";
}
~TimeKeeper()
{
cout<<"TimeKeeper destruction\n";
}
};
//本例只是说明虚析构函数作用,那些自定义拷贝构造函数、拷贝赋值函数不考虑
class AtomicClock : public TimeKeeper
{
public:
AtomicClock(const char* time) : TimeKeeper()
{
cout<<"AtomicClock construction\n";
this->m_time = new char[strlen(time) + 1];
strcpy(this->m_time, time);
}
~AtomicClock()
{
cout<<"AtomicClock destruction\n";
if (this->m_time != NULL)
delete m_time;
m_time = NULL;
}
private:
char *m_time;
};
调用
TimeKeeper *pTime = new AtomicClock("2014:5:24");
delete pTime;
pTime = NULL;
结果如下:
TimeKeeper construction
AtomicClock construction
TimeKeeper destruction
新创建一个AtomicClock对象,调用基类构造函数,再调用本类构造函数,然后将地址赋给基类指针,释放基类指针,只是调用基类析构函数,AtomicClock对象没有被析构,里面的动态成员没有被释放内存,导致内存泄露。
消除这种情况,只需要将基类析构函数声明为虚析构函数
virtual ~TimeKeeper()
{
cout<<"TimeKeeper destruction\n";
}
同样调用,结果如下
TimeKeeper construction
AtomicClock construction
AtomicClock destruction
TimeKeeper destruction
AtomicClock construction
AtomicClock destruction
TimeKeeper destruction
2、虚函数指针
一个class被当做base class,那些一般都是可以对derived class动态调用,实现对基类客制化,即同一个函数接口可以由derived class实现不同的行为,这通过虚函数接口实现,class中存在虚函数,那么几乎需要存在一个虚析构函数。
如果一个类不含有虚函数,而且不是当做基类,就为该class声明虚析构函数。类有虚函数会存在代价的
①移植困难
看下面代码
class Point
{
public:
Point(const int x, const int y) : m_x(x), m_y(y)
{
}
~Point() {}
private:
int m_x;
int m_y;
};
调用,赋给C语言结构体
Point point(4, 5);
typedef struct
{
int x;
int y;
} Point_C; //c语言结构体
Point_C *pc = (Point_C *)&point;
cout<<"X:"<<pc->x<<" Y :"<<pc->y<<endl;
调用结果,是我们想得到的
X:4 Y :5
将析构函数改成虚析构函数后
virtual ~Point() {}
同样调用结果
X:3359148 Y :4
看到的结果,X的值是一个指针的地址值(vptr,下面详说), Y是第二个值。
②增大内存开销
一个class存在虚函数,那些编译器为该类生成一个vptr(虚函数指针),占用内存4 byte,vptr主要是指向本类中的虚函数地址列表(vtbl),是一个数组,每个元素存放虚函数地址。vptr在类中空间部署,有些编译器是放在开头,如上面X就是虚函数地址,有些是放在后面。
如果系统中存在很多这样的函数,那会额外占用很大的内存,特别是对一些内存有限制的系统。
3、纯虚析构函数
抽象类
抽象类不能够实例化对象,只能通过指针指向derived class对象,实现多态性机制。但抽象类的定义需要有一个pure 虚函数,如果一个abstract class类中没有其他函数当做pure 虚函数,那么令析构函数为pure virtual 函数是一个良好的做法,这样该类既有虚析构函数,又可以将其抽象。
将TimeKeeper类定义为抽象类可如下,注意一点:纯虚析构函数必须定义,因为析构函数的运作方式是:先调用最深层的derived class析构函数,最后调用base class析构函数,如果不定义,则连接器会报错
virtual ~TimeKeeper() = 0
{
cout<<"TimeKeeper destruction\n";
}
并非所有的base类都用来设计成多态性,如实现对象的继承性,后续会详细说明
记住:
1、polymorphic(带多态性质的) base classes应该声明一个virtual 析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
2、Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性质(polymorphically),就不该声明virtual析构函数。
2、Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性质(polymorphically),就不该声明virtual析构函数。