虚析构函数

[align=center][size=large][b]虚析构函数[/b][/size][/align]

[size=large][b]一 、为何要单独讨论虚析构函数?虚函数在类中到底有什么作用?[/b][/size]

看如下代码:

#include <iostream>
using namespace std;

class enemytarget {
public:
enemytarget()
{
cout << "Call the Base Constructor!\n";
++numtargets;
cout << "numtargets :" << numtargets << endl;
}
enemytarget(const enemytarget&)
{
cout << "Call the Base Copy Constructor!\n";
++numtargets;
cout << "numtargets :" << numtargets << endl;
}
//virtual ~enemytarget()
~enemytarget()
{
cout << "Call the Base Destructor!\n";
--numtargets;
cout << "numtargets :" << numtargets << endl;
}

static size_t numberoftargets()
{ return numtargets; }

virtual bool destroy(); // 摧毁enemytarget对象后
// 返回成功

private:
static size_t numtargets; // 对象计数器
};

bool enemytarget::destroy()
{
if(numtargets == 0)
return true;
}

// 类的静态成员要在类外定义;
// 缺省初始化为0
size_t enemytarget::numtargets;


class enemytank: public enemytarget {
public:
enemytank()
{
cout << "Call the Inherit Constructor!\n";
++numtanks;
cout << "numbertanks :" << numtanks << endl;
}

enemytank(const enemytank& rhs)
: enemytarget(rhs)
{
cout << "Call the Inherit Copy Constructor!\n";
++numtanks;
cout << "numbertanks :" << numtanks << endl;
}

~enemytank()
{
cout << "Call the Inherit Destructor!\n";
--numtanks;
cout << "numbertanks :" << numtanks << endl;
}

static size_t numberoftanks()
{ return numtanks; }

virtual bool destroy();

private:
static size_t numtanks; // 坦克对象计数器
};

bool enemytank::destroy()
{
if(numtanks == 0)
return true;
}

size_t enemytank::numtanks;

int main()
{
enemytarget *targetptr = new enemytank[10];
//enemytank *targetptr = new enemytank[10];
delete [] targetptr;
return 0;
}


输出结果为:
Call the Base Constructor!
numtargets :1
Call the Inherit Constructor!
numbertanks :1
Call the Base Constructor!
numtargets :2
Call the Inherit Constructor!
numbertanks :2
Call the Base Constructor!
numtargets :3
Call the Inherit Constructor!
numbertanks :3
Call the Base Constructor!
numtargets :4
Call the Inherit Constructor!
numbertanks :4
Call the Base Constructor!
numtargets :5
Call the Inherit Constructor!
numbertanks :5
Call the Base Constructor!
numtargets :6
Call the Inherit Constructor!
numbertanks :6
Call the Base Constructor!
numtargets :7
Call the Inherit Constructor!
numbertanks :7
Call the Base Constructor!
numtargets :8
Call the Inherit Constructor!
numbertanks :8
Call the Base Constructor!
numtargets :9
Call the Inherit Constructor!
numbertanks :9
Call the Base Constructor!
numtargets :10
Call the Inherit Constructor!
numbertanks :10
Call the Base Destructor!
numtargets :9
Call the Base Destructor!
numtargets :8
Call the Base Destructor!
numtargets :7
Call the Base Destructor!
numtargets :6
Call the Base Destructor!
numtargets :5
Call the Base Destructor!
numtargets :4
Call the Base Destructor!
numtargets :3
Call the Base Destructor!
numtargets :2
Call the Base Destructor!
numtargets :1
Call the Base Destructor!
numtargets :0

可以看出子类的析构函数没有被调用,为何没有被调用?

enemytarget *targetptr = new enemytank[10];targetptr 是基类的对象,当我们[b]用基类的对象去delete派生类对象时会出现此类问题。当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。为了避免这个问题,只需要使enemytarget的析构函数为virtual。[/b]

如果把基类修改如下:

virtual ~enemytarget()
{
cout << "Call the Base Destructor!\n";
--numtargets;
cout << "numtargets :" << numtargets << endl;
}


输出结果为:
Call the Base Constructor!
numtargets :1
Call the Inherit Constructor!
numbertanks :1
Call the Base Constructor!
numtargets :2
Call the Inherit Constructor!
numbertanks :2
Call the Base Constructor!
numtargets :3
Call the Inherit Constructor!
numbertanks :3
Call the Base Constructor!
numtargets :4
Call the Inherit Constructor!
numbertanks :4
Call the Base Constructor!
numtargets :5
Call the Inherit Constructor!
numbertanks :5
Call the Base Constructor!
numtargets :6
Call the Inherit Constructor!
numbertanks :6
Call the Base Constructor!
numtargets :7
Call the Inherit Constructor!
numbertanks :7
Call the Base Constructor!
numtargets :8
Call the Inherit Constructor!
numbertanks :8
Call the Base Constructor!
numtargets :9
Call the Inherit Constructor!
numbertanks :9
Call the Base Constructor!
numtargets :10
Call the Inherit Constructor!
numbertanks :10
Call the Inherit Destructor!
numbertanks :9
Call the Base Destructor!
numtargets :9
Call the Inherit Destructor!
numbertanks :8
Call the Base Destructor!
numtargets :8
Call the Inherit Destructor!
numbertanks :7
Call the Base Destructor!
numtargets :7
Call the Inherit Destructor!
numbertanks :6
Call the Base Destructor!
numtargets :6
Call the Inherit Destructor!
numbertanks :5
Call the Base Destructor!
numtargets :5
Call the Inherit Destructor!
numbertanks :4
Call the Base Destructor!
numtargets :4
Call the Inherit Destructor!
numbertanks :3
Call the Base Destructor!
numtargets :3
Call the Inherit Destructor!
numbertanks :2
Call the Base Destructor!
numtargets :2
Call the Inherit Destructor!
numbertanks :1
Call the Base Destructor!
numtargets :1
Call the Inherit Destructor!
numbertanks :0
Call the Base Destructor!
numtargets :0

这种情况就避免了派生类析构函数不被调用的错误。


[b][size=large]二、什么情况下声明析构函数为虚函数,什么情况下不用声明为虚函数?[/size][/b]

如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。

看下面的例子:
// 一个表示2d点的类
class point {
public:
point(short int xcoord, short int ycoord);
~point();

private:
short int x, y;
};

如果一个short int占16位,一个point对象将刚好适合放进一个32位的寄存器中。另外,一个point对象可以作为一个32位的数据传给用c或fortran等其他语言写的函数中。但如果point的析构函数为虚,情况就会改变。

实现虚函数需要对象附带一些额外信息,以使对象在运行时可以确定该调用哪个虚函数。对大多数编译器来说,这个额外信息的具体形式是一个称为vptr(虚函数表指针)的指针。vptr指向的是一个称为vtbl(虚函数表)的函数指针数组。每个有虚函数的类都附带有一个vtbl。当对一个对象的某个虚函数进行请求调用时,实际被调用的函数是根据指向vtbl的vptr在vtbl里找到相应的函数指针来确定的。

虚函数实现的细节不重要(当然,如果你感兴趣,可以阅读条款m24),重要的是,如果point类包含一个虚函数,它的对象的体积将不知不觉地翻番,从2个16位的short变成了2个16位的short加上一个32位的vptr!point对象再也不能放到一个32位寄存器中去了。而且,c++中的point对象看起来再也不具有和其他语言如c中声明的那样相同的结构了,因为这些语言里没有vptr。所以,用其他语言写的函数来传递point也不再可能了,除非专门去为它们设计vptr,而这本身是实现的细节,会导致代码无法移植。

[b]所以基本的一条是,无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。[/b]

[size=large][b]三、当类里没有虚函数的时候,也会带来非虚析构函数问题。[/b][/size]

例子如下:

template<class t> // 基类模板
class array { // (来自条款13)
public:
array(int lowbound, int highbound);
~array();

private:
vector<t> data;
size_t size;
int lbound, hbound;
};

template<class t>
class namedarray: public array<t> {
public:
namedarray(int lowbound, int highbound, const string& name);
...

private:
string arrayname;
};


如果在应用程序的某个地方你将指向namedarray类型的指针转换成了array类型的指针,然后用delete来删除array指针,那你就会立即掉进“不确定行为”的陷阱中。

int main()
{
namedarray<int> *pna =
new namedarray<int>(10, 20, "impending doom");
array<int> *pa;
pa = pna; // namedarray<int>* -> array<int>*
delete pa; // 不确定! 实际中,pa->arrayname
// 会造成泄漏,因为*pa的namedarray
// 永远不会被删除
}

现实中,这种情形出现得比你想象的要频繁。让一个现有的类做些什么事,然后从它派生一个类做和它相同的事,再加上一些特殊的功能,这在现实中不是不常见。namedarray没有重定义array的任何行为——它继承了array的所有功能而没有进行任何修改——它只是增加了一些额外的功能。但非虚析构函数的问题依然存在.

所以一个类被定义为基类要注意虚析构函数的声明。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值