您的位置:首页 > 编程语言 > C语言/C++

More effective c++ 条款10(下)

2001-10-10 18:24 459 查看
条款10:在构造函数中防止资源泄漏(下)
你可能已经注意到BookEntry构造函数的catch块中的语句与在BookEntry的析构函数的语句几乎一样。这里的代码重复是绝对不可容忍的,所以最好的方法是把通用代码移入一个私有helperfunction中,让构造函数与析构函数都调用它。
classBookEntry{
public:
...//同上
private:
...
voidcleanup();//通用清除代码
};
voidBookEntry::cleanup()
{
deletetheImage;
deletetheAudioClip;
}
BookEntry::BookEntry(conststring&name,
conststring&address,
conststring&imageFileName,
conststring&audioClipFileName)
:theName(name),theAddress(address),
theImage(0),theAudioClip(0)
{
try{
...//同上
}
catch(...){
cleanup();//释放资源
throw;//传递异常
}
}
BookEntry::~BookEntry()
{
cleanup();
}

这就行了,但是它没有考虑到下面这种情况。假设我们略微改动一下设计,让
theImage
theAudioClip
是常量(constant)指针类型:
classBookEntry{
public:
...//同上
private:
...
Image*consttheImage;//指针现在是
AudioClip*consttheAudioClip;//const类型

};
必须通过BookEntry构造函数的成员初始化表来初始化这样的指针,因为再也没有其它地方可以给const指针赋值(参见EffectiveC++条款12)。通常会这样初始化theImage和theAudioClip:
//一个可能在异常抛出时导致资源泄漏的实现方法
BookEntry::BookEntry(conststring&name,
conststring&address,
conststring&imageFileName,
conststring&audioClipFileName)
:theName(name),theAddress(address),
theImage(imageFileName!=""
?newImage(imageFileName)
:0),
theAudioClip(audioClipFileName!=""
?newAudioClip(audioClipFileName)
:0)
{}

这样做导致我们原先一直想避免的问题重新出现:如果theAudioClip初始化时一个异常被抛出,theImage所指的对象不会被释放。而且我们不能通过在构造函数中增加try和catch语句来解决问题,因为try和catch是语句,而成员初始化表仅允许有表达式(这就是为什么我们必须在
theImage
theAudioClip
的初始化中使用?:以代替if
-
then
-
else
的原因)。

无论如何,在异常传递之前完成清除工作的唯一的方法就是捕获这些异常,所以如果我们不能在成员初始化表中放入try和catch语句,我们把它们移到其它地方。一种可能是在私有成员函数中,用这些函数返回指针,指向初始化过的theImage
theAudioClip
对象。
classBookEntry{
public:
...//同上
private:
...//数据成员同上
Image*initImage(conststring&imageFileName);
AudioClip*initAudioClip(conststring&
audioClipFileName);
};
BookEntry::BookEntry(conststring&name,
conststring&address,
conststring&imageFileName,
conststring&audioClipFileName)
:theName(name),theAddress(address),
theImage(initImage(imageFileName)),
theAudioClip(initAudioClip(audioClipFileName))
{}
//theImage被首先初始化,所以即使这个初始化失败也
//不用担心资源泄漏,这个函数不用进行异常处理。
Image*BookEntry::initImage(conststring&imageFileName)
{
if(imageFileName!="")returnnewImage(imageFileName);
elsereturn0;
}
//theAudioClip被第二个初始化,所以如果在theAudioClip
//初始化过程中抛出异常,它必须确保theImage的资源被释放。
//因此这个函数使用try...catch。
AudioClip*BookEntry::initAudioClip(conststring&
audioClipFileName)
{
try{
if(audioClipFileName!=""){
returnnewAudioClip(audioClipFileName);
}
elsereturn0;
}
catch(...){
deletetheImage;
throw;
}
}

上面的程序的确不错,也解决了令我们头疼不已的问题。不过也有缺点,在原则上应该属于构造函数的代码却分散在几个函数里,这令我们很难维护。
更好的解决方法是采用条款9的建议,把
theImage
theAudioClip
指向的对象做为一个资源,被一些局部对象管理。这个解决方法建立在这样一个事实基础上:theImage
theAudioClip
是两个指针,指向动态分配的对象,因此当指针消失的时候,这些对象应该被删除。auto_ptr类就是基于这个目的而设计的。(参见条款9)因此我们把theImage
theAudioClipraw
指针类型改成对应的auto_ptr类型。
classBookEntry{
public:
...//同上
private:
...
constauto_ptr<Image>theImage;//它们现在是
constauto_ptr<AudioClip>theAudioClip;//auto_ptr对象
};

这样做使得BookEntry的构造函数即使在存在异常的情况下也能做到不泄漏资源,而且让我们能够使用成员初始化表来初始化
theImage
theAudioClip
,如下所示:
BookEntry::BookEntry(conststring&name,
conststring&address,
conststring&imageFileName,
conststring&audioClipFileName)
:theName(name),theAddress(address),
theImage(imageFileName!=""
?newImage(imageFileName)
:0),
theAudioClip(audioClipFileName!=""
?newAudioClip(audioClipFileName)
:0)
{}

在这里,如果在初始化theAudioClip时抛出异常,theImage已经是一个被完全构造的对象,所以它能被自动删除掉,就象
theName
,
theAddress
thePhones
一样。而且因为theImage
theAudioClip
现在是包含在BookEntry中的对象,当BookEntry被删除时它们能被自动地删除。因此不需要手工删除它们所指向的对象。可以这样简化BookEntry的析构函数:
BookEntry::~BookEntry()
{}//nothingtodo!

这表示你能完全去掉BookEntry的析构函数。
综上所述,如果你用对应的auto_ptr对象替代指针成员变量,就可以防止构造函数在存在异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能象以前使用非const指针一样使用const指针,给其赋值。
在对象构造中,处理各种抛出异常的可能,是一个棘手的问题,但是auto_ptr(或者类似于auto_ptr的类)能化繁为简。它不仅把令人不好理解的代码隐藏起来,而且使得程序在面对异常的情况下也能保持正常运行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: