MoreEffectiveC++:防止资源泄漏

MoreEffectiveC++:防止资源泄漏,第1张

MoreEffectiveC++:防止资源泄漏,第2张

如果你正在开发一个具有多媒体功能的地址簿程序。这种通讯录不仅可以存储姓名、地址、电话号码等常用的文字信息,还可以存储照片和声音(可以给出其姓名的正确读音)。

要实现这个通讯录,可以这样设计:

Class Image {//用于图像数据
public:
Image(const string & Image data filename);
...
};

Class AudioClip {//用于声音数据
public:
audio clip(const string & audiodatafilename);
...
};

班级电话号码{...};//用于存储通讯录中的电话号码
类BookEntry {//条目
public:
BookEntry(const string & name,
conststring & address = " ",
const string & image filename = " ",
const string & audioClipFileName = " ";
~ BookEntry();
//通过此功能添加电话号码
void添加电话号码(const phone number & number);
...
private:
string theName;//人名
字符串theAddress//他们的地址
列出了电话号码;//他的电话号码
Image * the Image;//他们的图像
audio clip * the audio clip;//一段他们的声音
};

通讯录中的每个条目都有名字数据,所以你需要一个带参数的构造函数(见第3条),但其他内容(地址、图像和声音文件名)是可选的。注意,链表类(list)应该用来存储电话号码,这是标准C++类库(STL)中的一个容器类。(见有效C++第49条和本书第35条)

有一种简单的方法来编写BookEntry构造函数和析构函数:

BookEntry::BookEntry(Const string & name,const string& address,
Const string & image filename,
Const string & audioclip filename)
:the name(名称),theAddress(地址),
theImage(0),the audioclip(0)
{
if(image filename!= " "){
the Image = new Image(Image filename);
}
if(audioClipFileName!= " "){
theAudioClip = new audio clip(audio clip filename);
}
}
BookEntry::~ BookEntry()
{
删除图像;
删除音频剪辑;
}

构造函数将指针Image和AudioClip初始化为空,如果这些指针对应的构造函数参数不是空,则让它们指向真实对象。析构函数负责删除这些指针,以确保BookEntry对象不会泄漏资源。因为C++保证删除空指针是安全的,所以BookEntry的析构函数在删除之前不需要检测这些指针是否指向某些对象。

看起来一切都很好,正常情况下真的很好,但是在非正常情况下(比如发生异常的时候)恐怕他们就不会好了。

想想如果BookEntry的构造函数正在执行,抛出了异常,会发生什么。:

if (audioClipFileName!= " "){
theAudioClip = new audio clip(audio clip filename);
}

抛出异常,要么是因为运算符new(见第8条)无法为AudioClip分配足够的内存,要么是因为AudioClip的构造函数本身抛出异常。无论什么原因,如果在BookEntry构造函数中抛出异常,该异常将被传递到创建BookEntry对象的位置(在构造函数体之外。译者注)。

现在假设在创建theAudioClip对象时抛出了一个异常(并且程序的控制权被传递给了BookEntry构造函数的外部),那么谁将负责删除图像所指向的对象呢?很明显,答案应该是BookEntry做的,但是这个假设的答案是错误的。BookEntry将永远不会被调用。

C++只能删除完全构造的对象。只有当对象的构造函数完全运行时,才能完整地构造对象。因此,如果BookEntry对象B被创建为本地对象,则如下所示:

void testBookEntryClass()
{
BookEntry b(" Addison-Wesley出版公司"," One Jacob Way,Reading,MA 01867 ");
...
}

并且在构造B的过程中抛出异常,不会调用B的析构函数。而如果试图采取积极的措施来处理异常,也就是在异常发生时调用delete,如下所示:

void testBookEntryClass()
{
BookEntry * PB = 0;
try {
Pb = new BookEntry(" Addison-Wesley出版公司"," One Jacob Way,Reading,MA 01867 ");
...
}
catch(...){//捕捉所有异常
删除Pb;//删除pb,抛出异常时
抛出;//将异常传递给调用方
}

删除Pb;//正常删除Pb
}

你会发现在BookEntry构造函数中为Image分配的内存还是丢失了,因为如果新操作没有成功完成,程序不会把值赋给pb。如果BookEntry的构造函数抛出异常,那么pb将是一个空值,所以在catch块中删除它除了让你感觉良好之外,什么也不会做。用智能指针类auto_ptr(见第9条)替换raw BookEntry*不会有任何效果,因为在新操作成功完成之前,pb是不赋值的。
c++拒绝为没有完成构造操作的对象调用析构函数,而不是故意给你制造困难,是有原因的。原因是:很多时候,这样做是没有意义的,甚至是有害的。如果对一个没有完成构造操作的对象调用析构函数,析构函数怎么做?唯一的方法是给每个对象添加一些字节来表示构造函数已经执行了多少步。然后让析构函数检测这些字节,决定执行哪些操作。这样的记录会减慢析构函数的运行速度,使对象的大小变大。C++避免了这种开销,但代价是不能自动删除部分构造的对象。(有关程序行为和效率之间折衷的示例,请参见Effective C++的第13条。)

因为当对象在构造中抛出异常时,C++不负责清除对象,所以您必须重新设计您的构造函数,使它们自己清除异常。通常的方法是捕捉所有的异常,然后执行一些清除代码,最后重新抛出异常保持转发。如下所示,在BookEntry构造函数中使用此方法:

BookEntry::BookEntry(const string & name,
const string& address,
const string & image filename,
const string & audioclipfilename]
:名称(name),地址(address),
图像(0),音频剪辑(0)
{
try {//这个try块是新添加的。= " "){
the Image = new Image(Image filename);
}
if(audioClipFileName!= " "){
theAudioClip = new audio clip(audio clip filename);
}
}
catch(...){//捕捉所有异常
删除图像;//完成必要的清除代码
删除theAudioClip
扔;//继续传递异常
}
}

不用担心BookEntry中的非指针数据成员,在调用类的构造函数之前,数据成员会自动初始化。因此,如果BookEntry构造函数体开始执行,那么对象的名称、地址和电话的数据成员就已经被完全构造好了。这些数据可以看作是完全构造的对象,所以它们会自动释放,不需要你的干预。当然,如果这些对象的构造函数调用了可能抛出异常的函数,哪些构造函数必须考虑捕捉异常并完成必要的清除操作,才允许它们继续传递。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » MoreEffectiveC++:防止资源泄漏

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情