C++异常处理机制总结

来源:互联网 发布:链接wifi无法加入网络 编辑:程序博客网 时间:2024/06/05 15:41

参考文档:《C++编程思想》《C++Primer》《More effective C++

 

一、             传统的错误处理机制:

 

1.         返回值或全局错误状态标志。

缺点:需要冗长的错误检查代码。

2.         C standard Library中的信号处理系统,signal函数。

缺点:信号处理机制比较复杂;耦合度高;复杂系统中的信号容易产生冲突。

3.         C standard Library中的非局部跳转函数,setjmplongjmp

缺点: C++的类析构函数不会被调用,对象不能正确被清理,造成内存泄漏;耦合度高。

 

二、             C++异常处理机制介绍:

 

1.         语法:


 

 

2.         说明:

1)        throw 可以抛出一个任意类型的变量(或表达式)(包括内置类型如int的变量,或者自定义的类型的变量)catch 按照被抛出变量的编译时类型进行匹配,找到第一个匹配的类型即进入异常处理。

2)        异常处理是为了保证使程序能够不异常退出。

3)        建议:尽量不要使用throw抛出内置类型的变量。

4)        如果throw抛出的异常找不到匹配的类型,最终程序将调用C standard Libraryterminate函数,程序将异常退出。

5)        当程序跳出try块时,try块内的局部变量被自动释放,对象的析构函数被调用。所以,为了保证程序不异常退出,应该保证析构函数不会抛出异常。

6)        调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。

7)        异常匹配时,只允许三种类型转换const与非const;派生类与基类;数组与指针。(注意:不允许算术转换.)

8)        建议:catch子句的次序必须反映类型层次,派生类放到基类前面。

9)        throw出的对象称为异常对象(exception object),由编译器管理catch接受到的对象如果不是引用或指针的话,则进行对象拷贝。但是异常对象是程序结束才被释放

10)     异常可以发生在构造函数中或者构造函数初始化式中。注意:如果异常发生在构造函数中,对象的析构函数将不会被调用!所以需要在构造函数中进行try-catch自己释放资源。另外,为了处理构造函数初始化式中可能发生的异常,语法应该修改为如下:

 

11)     标准库异常类定义在<exception><stdexcep>头文件中。

 

 

3.         重新抛出:

catch块或被catch块调用的函数中,可以用”throw;”语句(throw空对象)将异常重新抛出。

 

4.         异常规格说明(exception specification)

说明函数将会抛出什么类型的异常。

例子:

 

1)        如果函数运行时抛出了其他类型的异常,程序将会调用标准库的unexpected函数,该函数将调用terminate退出程序。

2)        派生类的虚函数的异常规格说明只能和基类一样或比基类更严格,不能增加新的异常类型。

3)        注意:一般只会使用throw()来说明一个函数是安全的,不会抛出任何异常,这样编译器就可以对调用该函数的代码做出优化。一般不会使用其他的异常规格说明。

 

5.         标准异常库:(见<exception><stdexcep><new><typeinfo>头文件)

 

       其中,类exception的定义如下:

 

 

三、             使用异常处理机制的建议:

 

来自《C++编程思想》和《More Effective C++》的建议:

1.         不要使用异常的情形:

1)        绝对不要在异步事件中使用异常。如使用了信号机制的系统、中断处理程序等。

2)        不要在处理简单错误的时候使用异常。一般情况下,自己可以处理的错误就直接处理,只有当在此处处理不了的错误才抛出到更大的语境中。

3)        不要将异常用于流程控制,比如代替switch语句。因为异常处理效率非常低,而且编译器会做出很多程序员不知道的事情。

4)        遇到不可恢复的错误时,最好不用处理异常,直接将异常交给操作系统处理即可。

2.         应该使用异常的情形:

1)        遇到可以修正的错误,通过一些行为可以使程序继续执行

2)        在当前的语境中不能完全处理的错误,但有可能在较高层的语境中可以处理,这时,可以通过将一个同样类型或者不同类型的异常抛出;

3)        发生不足以让程序退出的错误,但是你认为该错误是致命错误的时候,可以通过抛出异常便于及时发现故障而终止程序

4)        为了简化错误处理。

 

3.         对使用异常的建议(1,4,5,6MoreEffectiveC++)

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对象来完成相同的效应:

 

  值得一提的是,在VC6VC2005编译器下,默认的new操作符失败的行为也是简单的返回一个NULL指针,它们并不是遵循C++标准抛出一个std::bad_alloc异常。

 

 

 

 

 

 

原创粉丝点击