当前位置:天才代写 > tutorial > C语言/C++ 教程 > C++异常和错误处理惩罚履历谈

C++异常和错误处理惩罚履历谈

2017-11-04 08:00 星期六 所属: C语言/C++ 教程 浏览:356

副标题#e#

取代 try / catch / throw 的凡是做法是返回一个返回代码(有时称为错误代码),譬喻,printf(), scanf() 和 malloc()就是这样事情的:挪用者通过if等语句来测试返回值判定函数是否乐成。

尽量返回代码技能有时是最适当的错误处理惩罚技能,但会增加不须要的if语句这样的令人讨厌的结果。

质量降级:众所周知,条件语句大概包括的错误约莫十倍于其他范例的语句。因此,在其他都沟通时,假如你能从代码中消除条件语句,你会获得更结实的代码。

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

增加开拓本钱:不须要的节制流程的巨大性增加了寻找bug,修复bug,和测试的事情。

因此,相对付通过返回代码和if来陈诉错误,利用try / catch / throw所发生更少有bug,更低的开拓本钱和更快面市的代码。

如那里理惩罚结构函数的失败?

结构函数没有返回范例,所以返回错误代码是不行能的。因此抛出异常是标志结构函数失败的最好要领。

假如你没有可能不肯意利用异常,这里有一种要领。假如结构函数失败了,结构函数可以把工具带入一种“僵尸”状态。你可以通过配置一个内部状态位使工具就象死了一样,纵然从技能上来说,它仍然在世。然后插手一个查询(“查看员”)成员函数,以便类的用户可以或许通过查抄这个“僵尸位”来确定工具是真的在世照旧已经成为僵尸(也就是一个“在世的死工具”)。你也许想有另一个成员函数来查抄这个僵尸位,而且当工具并不是真正在世的时候,执行一个 no-op(可能是更令人讨厌的如 abort())。这样做真的不大度,可是假如你不能(可能不想)利用异常的话,这是最好的要领了。

如那里理惩罚析构函数的失败?

往log文件中写一个动静。但不要抛出异常!

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%的环境下,这是一个好法则。

假如结构函数会抛出异常,我该奈何处理惩罚资源?

工具中的每个数据成员应该清理本身。

假如结构函数抛出异常,工具的析构函数将不会运行。假如你的工具需要取消一些已经做了的行动(如分派了内存,打开了一个文件,可能锁定了某个信号量),这些需要被取消的行动必需被工具内部的一个数据成员记着。

譬喻,应该将分派的内存赋给工具的一个“智能指针”成员工具Fred,而不是分派内存给未被初始化的Fred* 数据成员。这样当该智能指针消亡时,智能指针的析构函数将会删除Fred工具。尺度类auto_ptr就是这种“智能指针”类的一个例子。你也可以写你本身的引用计数智能指针。

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

假如你要做简直实需要字符串,那么不要利用char数组,因为数组会带来贫苦。应该用一些雷同字符串类的工具来取代。


#p#副标题#e#

譬喻,假设你要获得一个字符串的拷贝,随意修改这个拷贝,然后在修悔改的拷贝的字符串末端添加其它的字符串。字符数组要领将是这样:

#p#分页标题#e#

void userCode(const char* s1, const char* s2)
{
// 建造s1的拷贝:
char* copy = new char[strlen(s1) + 1];
strcpy(copy, s1);
// 此刻我们有了一个指向分派了的自由存储的内存的指针,
// 我们需要用一个try块来防备内存泄漏:
try {
// ... 此刻我们随意乱动这份拷贝...
// 将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 块,纵然或人会在某处抛出异常。

 

    关键字:

天才代写-代写联系方式