--
分类: C/C++
2014-03-02 18:16:44
参考文档:《C++编程思想》《C++Primer》《More effective C++》
ZZ http://blog.csdn.net/mulinb/article/details/1763240
一、 传统的错误处理机制:
1. 返回值或全局错误状态标志。
缺点:需要冗长的错误检查代码。
2. C standard Library中的信号处理系统,signal函数。
缺点:信号处理机制比较复杂;耦合度高;复杂系统中的信号容易产生冲突。
3. C standard Library中的非局部跳转函数,setjmp和longjmp。
缺点: C++的类析构函数不会被调用,对象不能正确被清理,造成内存泄漏;耦合度高。
二、 C++异常处理机制介绍:
1. 语法:
2. 说明:
1) throw 可以抛出一个任意类型的变量(或表达式)(包括内置类型如int的变量,或者自定义的类型的变量),catch 按照被抛出变量的编译时类型进行匹配,找到第一个匹配的类型即进入异常处理。
2) 异常处理是为了保证使程序能够不异常退出。
3) 建议:尽量不要使用throw抛出内置类型的变量。
4) 如果throw抛出的异常找不到匹配的类型,最终程序将调用C standard Library的terminate函数,程序将异常退出。
5) 当程序跳出try块时,try块内的局部变量被自动释放,对象的析构函数被调用。所以,为了保证程序不异常退出,应该保证析构函数不会抛出异常。
6) 调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。
7) 异常匹配时,只允许三种类型转换:const与非const;派生类与基类;数组与指针。(注意:不允许算术转换.)
8) 建议:catch子句的次序必须反映类型层次,派生类放到基类前面。
9) throw出的对象称为异常对象(exception object),由编译器管理,catch接受到的对象如果不是引用或指针的话,则进行对象拷贝。但是异常对象是程序结束才被释放。
10) 异常可以发生在构造函数中或者构造函数初始化式中。注意:如果异常发生在构造函数中,对象的析构函数将不会被调用!所以需要在构造函数中进行try-catch自己释放资源。另外,为了处理构造函数初始化式中可能发生的异常,语法应该修改为如下:
11) 标准库异常类定义在
3. 重新抛出:
在catch块或被catch块调用的函数中,可以用”throw;”语句(throw空对象)将异常重新抛出。
4. 异常规格说明(exception specification):
说明函数将会抛出什么类型的异常。
例子:
1) 如果函数运行时抛出了其他类型的异常,程序将会调用标准库的unexpected函数,该函数将调用terminate退出程序。
2) 派生类的虚函数的异常规格说明只能和基类一样或比基类更严格,不能增加新的异常类型。
3) 注意:一般只会使用throw()来说明一个函数是安全的,不会抛出任何异常,这样编译器就可以对调用该函数的代码做出优化。一般不会使用其他的异常规格说明。
5. 标准异常库:(见
其中,类exception的定义如下
三、 使用异常处理机制的建议:
来自《C++编程思想》和《More Effective C++》的建议:
1. 不要使用异常的情形:
1) 绝对不要在异步事件中使用异常。如使用了信号机制的系统、中断处理程序等。
2) 不要在处理简单错误的时候使用异常。一般情况下,自己可以处理的错误就直接处理,只有当在此处处理不了的错误才抛出到更大的语境中。
3) 不要将异常用于流程控制,比如代替switch语句。因为异常处理效率非常低,而且编译器会做出很多程序员不知道的事情。
4) 遇到不可恢复的错误时,最好不用处理异常,直接将异常交给操作系统处理即可。
2. 应该使用异常的情形:
1) 遇到可以修正的错误,通过一些行为可以使程序继续执行;
2) 在当前的语境中不能完全处理的错误,但有可能在较高层的语境中可以处理,这时,可以通过将一个同样类型或者不同类型的异常抛出;
3) 发生不足以让程序退出的错误,但是你认为该错误是致命错误的时候,可以通过抛出异常便于及时发现故障而终止程序;
4) 为了简化错误处理。
3. 对使用异常的建议(1,4,5,6见MoreEffectiveC++):
1) 慎重使用异常规格说明:当不确定函数抛出何种异常时,最好不要使用异常规格说明。
2) 尽量使用标准异常库的异常类:编写自定义的异常类之前,先查看标准异常库。如果标准异常库没有需要的语义的异常,尽量从标准异常类派生出自定义的异常类。
3) 建立自己的异常类层次结构。
4) 尽量通过引用来捕捉异常,原因有二:防止对象拷贝;防止对象slicing。异常对象由编译器统一管理,可以放心使用引用来操作。
5) 可以在构造函数中抛出异常:如果构造函数发生故障而没有及时发现,程序继续运行会造成不可预料的灾难性结果,这种情况下可以在构造函数中抛出异常。
6) 不要在析构函数中抛出异常。所以有时候需要在析构函数中处理异常。
四、 编码规范:
1. 函数的参数检查不合法时,抛出标准库的invalid_argument(logic_error子类)异常。
2. 调用函数时,应该检查logic_error异常,并做相应处理,该异常不足以使程序终止。
3. 发生致命逻辑错误或者不可恢复错误时,不要捕捉抛出的异常,直接将其交给操作系统处理,退出程序。
4. 尽量使用标准异常库的异常类,没有合适的标准异常时,建立自己的异常层次结构。
5. 自定义异常类时,应该从标准异常库中的类派生,异常类的名称应该能反映出错误的类型。
6. 使用引用来捕捉异常。
7. 不要使用catch(…)。
8. 捕捉到未知异常时,要重新抛出,以免发生故障后继续运行程序,造成不可预料的后果。
9. 尽量将资源释放写入析构函数,这样防止发生异常时,资源不能释放。
10. 不要使用异常规格说明。??讨论??
11. 建立一个系统的异常基类,将所有系统中发生的异常都封装成该基类的子类。 ??讨论??
五、 例子:
六、 对使用new操作符分配内存失败的说明:
较新的C++标准中规定,普通的new操作符失败时,抛出std::bad_alloc异常。但是在以前,new失败是简单的返回一个NULL指针。在一定的环境下,返回一个NULL指针来表示一个失败依然是一个不错的选择。C++标准委员会意识到这个问题,所以他们决定定义一个特别的new操作符版本,这个版本返回0表示失败。这就是nothrow new。
nothrow new只是简单的给operator new加了一个参数。
相关代码如下
所以,使用nothrow new时,可以如下使用:
也可以创建你自己的nothrow_t对象来完成相同的效应:
值得一提的是,在VC6和VC2005编译器下,默认的new操作符失败的行为也是简单的返回一个NULL指针,它们并不是遵循C++标准抛出一个std::bad_alloc异常。