分享
 
 
 

C++ FAQ Lite[17]--异常和错误处理(更新)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

[17] 异常和错误处理

(Part of C++ FAQ Lite, ]Copyright © 1991-2001, Marshall Cline, cline@parashift.com)

简体中文版翻译:申旻nicrosoft@sunistudio.com东日制作室东日文档

FAQs in section [17]:

][17.1] try / catch / throw 通过哪些方法来改善软件质量?

][17.2] 如何处理构造函数的失败?

][17.3] 如何处理析构函数的失败?

][17.4] 如果构造函数会抛出异常,我该怎样处理资源?

][17.5] 当别人抛出异常时,我如何改变字符数组的字符串长度来防止内存泄漏?

[17.1] try / catch / throw 通过哪些方法来改善软件质量?

[Recently created (on 7/00). ]Click here to go to the next FAQ in the "chain" of recent changes.]

通过排除使用if语句的一个理由。

代替 try / catch / throw 的通常做法是返回一个返回代码(有时称为错误代码),调用者通过诸如if的条件语句明确地测试。例如,printf(), scanf() 和 malloc()就是这样工作的:调用者被假定为会测试返回值来判断函数是否成功。

尽管返回代码技术有时是最适当的错误处理技术,但会产生增加不必要的if语句这样的令人讨厌的效果。

质量降级:众所周知,条件语句可能包含的错误大约十倍于其他类型的语句。因此,在其他都相同时,如果你能从代码中消除条件语句,你会得到更健壮的代码。

推迟面市:由于条件语句是分支点,而它们关系到白盒法测试时的测试条件的个数,因此不必要的条件语句会增加测试的时间总量。如果你没有走过每个分支点,那么你的代码中就会有在测试中没有被执行过的指令,直到用户/客户发现它,那就糟了。

增加开发成本:不必要的控制流程的复杂性增加了寻找bug,修复bug,和测试的工作。

因此,相对于通过返回代码和if来报告错误,使用try / catch / throw所产生更少有bug,更低的开发成本和更快面市的代码。当然,如果你的团队没有任何使用try / catch / throw的经验,你也许想先在一个玩具性的项目上使用一下,以便确定你明白正在做的事情——在把武器拿上真枪实弹的前线前,总应该演练一下吧。

[ Top | Bottom | Previous section | Next section ]

[17.2] 如何处理构造函数的失败?

[Recently fixed typo ("its" vs. "it's") thanks to Wes Bemont (on 4/01). ]Click here to go to the next FAQ in the "chain" of recent changes.]

抛出一个异常。

构造函数没有返回类型,所以返回错误代码是不可能的。因此抛出异常是标记构造函数失败的最好方法。

如果你没有或者不愿意使用异常,这里有一种方法。如果构造函数失败了,构造函数可以把对象带入一种“僵尸”状态。你可以通过设置一个内部状态位使对象就象死了一样,即使从技术上来说,它仍然活着。然后加入一个查询(“检察员”)成员函数,以便类的用户能够通过检查这个“僵尸位”来确定对象是真的活着还是已经成为僵尸(也就是一个“活着的死对象”)。你也许想有另一个成员函数来检查这个僵尸位,并且当对象并不是真正活着的时候,执行一个 no-op(或者是更令人讨厌的如 abort())。这样做真的不漂亮,但是如果你不能(或者不想)使用异常的话,这是最好的方法了。

[ Top | Bottom | Previous section | Next section ]

[17.3] 如何处理析构函数的失败?

[Recently created (on 7/00). ]Click here to go to the next FAQ in the "chain" of recent changes.]

往log文件中写一个消息。或打电话给Tilda舅妈。但不要抛出异常!

以下是为什么(扣好你的安全带):

C++的规则是你绝对不可以在另一个异常的被称为“栈展开(stack unwinding)”的过程中时,从析构函数抛出异常。举例来说,如果某人写了throw Foo(),栈会被展开,以至throw Foo()和 } catch (Foo e) { 之间的所有的栈页面被弹出。这被称为栈展开(statck unwinding)

在栈展开时,栈页面中的所有的局部对象会被析构。如果那些析构函数之一抛出异常(假定它抛出一个Bar对象),C++运行时系统会处于无法决断的境遇:应该忽略Bar并且在} catch (Foo e) { 结束?应该忽略Foo并且寻找 } catch (Bar e) { ?没有好的答案——每个选择都会丢失信息。

因此C++语言担保,当处于这一点时,会调用terminate()来杀死进程。突然死亡。

防止这种情况的简单方法是不要从析构函数中抛出异常。但如果你真的要聪明一点,你可以说当处理另一个异常的过程中时,不要从析构函数抛出异常。但在第二种情况中,你处于困难的境地:析构函数本身既需要代码处理抛出异常,还需要处理一些“其他东西”,调用者没有当析构函数检测到错误时会发生什么的担保(可能抛出异常,也可能做一些“其他事情”)。因此完整的解决方案非常难写。因此索性就做一些“其他事情”。也就是,不要从析构函数中抛出异常。

当然,由于总有一些该规则无效的境况,这些话不应该被“引证”。但至少99%的情况下,这是一个好规则。

[ Top | Bottom | Previous section | Next section ]

[17.4] 如果构造函数会抛出异常,我该怎样处理资源?

对象中的每个数据成员应该清理自己。

如果构造函数抛出异常,对象的析构函数将不会运行。如果你的对象需要撤销一些已经做了的动作(如分配了内存,打开了一个文件,或者锁定了某个信号量),这些需要被撤销的动作必须被对象内部的一个数据成员记住。

例如,应该将分配的内存赋给对象的一个“智能指针”成员对象Fred,而不是分配内存给未被初始化的Fred* 数据成员。这样当该智能指针消亡时,智能指针的析构函数将会删除Fred对象。标准类auto_ptr就是这种“智能指针”类的一个例子。你也可以]写你自己的引用计数智能指针。你也可以]用智能指针来指向磁盘记录或者其它机器上的对象

[ Top | Bottom | Previous section | Next section ]

[17.5] 当别人抛出异常时,我如何改变字符数组的字符串长度来防止内存泄漏?

[Recently rewrote the last half (on 7/00). ]Click here to go to the next FAQ in the "chain" of recent changes.]

如果你要做的确实需要字符串,那么不要使用char数组,因为]数组会带来麻烦。应该用一些类似字符串类的对象来代替。

例如,假设你要得到一个字符串的拷贝,随意修改这个拷贝,然后在修改过的拷贝的字符串末尾添加其它的字符串。字符数组方法将是这样:

void userCode(const char* s1, const char* s2)

{

// 制作s1的拷贝:

char* copy = new char[strlen(s1) + 1];

strcpy(copy, s1);

// 现在我们有了一个指向分配了的自由存储的内存的指针,

// w我们需要用一个try块来防止内存泄漏:

try {

// ... n现在我们随意乱动这份拷贝...

// 将s2 添加到被修改过的 copy 末尾:

// ... [在此处重分配 copy] ...

char* copy2 = new char[strlen(copy) + strlen(s2) + 1];

strcpy(copy2, copy);

strcpy(copy2 + strlen(copy), s2);

delete[] copy;

copy = copy2;

// ... 最后我们再次随意乱动拷贝...

} catch (...) {

delete[] copy; // 得到一个异常时,防止内存泄漏

throw; // 重新抛出当前的异常

}

delete[] copy; // 没有得到异常时,防止内存泄漏

}

象这样使用char*s是单调的并且容易发生错误。为什么不使用一个字符串类的对象呢?你的编译器也许提供了一个字符串类,而且它可能比你自己写的char*s更快,当然也更简单、更安全。例如,如果你使用了]标准化委员会的字符串类std::string,你的代码看上去就会象这样:

#include <string> // 让编译器找到 std::string 类

void userCode(const std::string& s1, const std::string& s2)

{

std::string copy = s1; // 制作s1的拷贝

// ... 现在我们随意乱动这份拷贝...

copy += s2; // A将 s2 添加到被修改过的拷贝末尾

// ... 最后我们再次随意乱动拷贝...

}

函数体中总共只有两行代码,而前一个例子中有12行代码。节省来自内存管理,但也有一些是来自于我们不必先式的调用strxxx()例程。这里有一些重点:

由于std::string自动处理了内存管理,当增长字符串时,我们不需要先式地写任何分配内存的代码。

由于std::string自动处理了内存管理,在结束时不需要 delete[] 任何东西。

由于std::string自动处理了内存管理,在第二个例子中不需要 try 块,即使某人会在某处抛出异常。

[ Top | Bottom | Previous section | Next section ]

E-mail the author

[ C++ FAQ Lite | Table of contents | Subject index | ]About the author | | ]Download your own copy ]

Revised Apr 8, 2001

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有