Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1417243
  • 博文数量: 241
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 2253
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-11 22:27
个人简介

--

文章分类

全部博文(241)

文章存档

2021年(3)

2019年(6)

2018年(1)

2017年(9)

2016年(21)

2015年(50)

2014年(125)

2013年(26)

我的朋友

分类: 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中的非局部跳转函数,setjmplongjmp

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

 

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

 

1.         语法:

  1. try  
  2. {  
  3.     throw 3.0;  
  4. }  
  5. catch (int) //如果处理块内不需要该变量,可以省略形参  
  6. {  
  7.     cout << "int exception happened!" << endl;  
  8. }  
  9. catch (double d)  
  10. {  
  11.     cout << d << " double exception happened!" << endl;  
  12. }  
  13. catch (…)  
  14. {  
  15.     cout << "unknown exception happened!" << endl;  
  16. }  

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自己释放资源。另外,为了处理构造函数初始化式中可能发生的异常,语法应该修改为如下:

  1. //normal constructor  
  2. UserClass(): m_nA(1), m_nB(2) { /*constructor body*/ }  
  3.   
  4. //constructor using function try block  
  5. UserClass() try: m_nA(1), m_nB(2){ /*constructor body*/ }catch(…){ }  

11)     标准库异常类定义在头文件中

3.         重新抛出:

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

 

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

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

例子:

  1. void func(int i) throw (runtime_error); //说明该函数func有可能抛出runtime_error异常  
  2.   
  3. void func(int i) throw(); //说明函数func不会抛出任何异常  

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

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

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

 

5.         标准异常库:(见头文件 

       其中,类exception的定义如下

  1. class exception  
  2. {  
  3. public:  
  4.     exception();  
  5.     exception(const char* const &);  
  6.     exception(const exception&);  
  7.     exception& operator= (const exception&);  
  8.     virtual ~exception();  
  9.     virtual char* what() const;  //获得信息  
  10. private:  
  11.     const char* _m_what;  
  12.     int _m_doFree;  
  13. };  

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

 

来自《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.     建立一个系统的异常基类,将所有系统中发生的异常都封装成该基类的子类。 ??讨论??

五、             例子:

  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. void func(int i)  
  6. {  
  7.     if (i>100)  
  8.     {  
  9.         throw invalid_argument("invalid argument: argument can not be bigger than 100");  
  10.     }  
  11.     //do something…  
  12. }  
  13.    
  14. int main()  
  15. {  
  16.     int i;  
  17.     cout << "Please input a number less than 100:" << endl;  
  18.     cin >> i;  
  19.    
  20.     try  
  21.     {  
  22.         func(i);  
  23.     }  
  24.     catch (logic_error& e)  //caught by reference  
  25.     {  
  26.         cout << "invalid input: " << e.what() << endl; //logic_error, can be handled, do not need to abort  
  27.     }  
  28.     catch (exception&)  
  29.     {  
  30.         //do something…  
  31.          throw;  //unknown exception caught, throw again  
  32.     }  
  33.    
  34.     return 0;  
  35. }

六、             对使用new操作符分配内存失败的说明:

较新的C++标准中规定,普通的new操作符失败时,抛出std::bad_alloc异常。但是在以前,new失败是简单的返回一个NULL指针。在一定的环境下,返回一个NULL指针来表示一个失败依然是一个不错的选择。C++标准委员会意识到这个问题,所以他们决定定义一个特别的new操作符版本,这个版本返回0表示失败。这就是nothrow new

nothrow new只是简单的给operator new加了一个参数。

相关代码如下

  1. class nothrow_t  // in namespace std  
  2. {}; //empty class  
  3.   
  4. extern const nothrow_t  nothrow;  //an object , in namespace std  
  5.    
  6. //declarations from   
  7. void *  operator new (size_t size, const std::nothrow_t &);  
  8.   
  9. //array version  
  10. void *  operator new[] (size_t size, const std::nothrow_t &);   

              所以,使用nothrow new时,可以如下使用:

  1. #include   
  2. #include  // for std::cerr  
  3. #include  // for std::exit()  
  4. Task * ptask = new (std::nothrow)  Task;  
  5. if (!ptask)  
  6. {  
  7.       std::cerr<<"allocation failure!";  
  8.       std::exit(1);  
  9. }       

              也可以创建你自己的nothrow_t对象来完成相同的效应:

  1. #include   
  2. std::nothrow_t  nt;  
  3. Task * ptask = new (nt)  Task; //user-defined argument  
  4. if (!ptask)  
  5. //...  

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

阅读(2507) | 评论(0) | 转发(0) |
0

上一篇:MySQL常用命令

下一篇:C++实现string类

给主人留下些什么吧!~~