C++异常处理

转载自:http://blog.csdn.net/sadjason/article/details/22225231

       C++标准库由不同的成分构成。来源不同(不是由某个人或某个组织以某种统一的形式弄成的),设计与实现风格迥异。而错误处理和异常处理正是这种差异的一个典型代表。标准程序库中有一部分,例如string classes,支持具体的错误处理,它们检查所有可能发生的错误,并于错误发生时抛出异常。至于其他的部分如STL和valarrays,效率重于安全,因此几乎不检测逻辑错误,并且只在执行期(runtime)发生错误时才抛出异常。

有哪些异常?
    语言本身或标准程序库所抛出的所有异常都派生自exception,主要包括三类:
a. C++语言本身所支持的异常(也即不管是string还是STL,只要是C++代码都可能会出现的异常);
     bad_alloc、bad_cast、bad_typeid、bad_exception

b. C++标准库所所发出的异常
     ios_base::failure、logic_error、domain_error、invalid_argument、length_error、out_of_range
     P.S:除了ios_base::failure和logic_error是平级之外,logic_error是其他几个异常的基类;

c. 程序作用域之外发出的异常
     runtime_exception、range_error、overflow_error、underflow_error

                                                                  

        假设有一个getData()函数,它的作用是返回某种内置数据类型,它可能是以下某种定义方式:

char getData(...) { ... }     // char型返回值
int * getData( ... ) { ... }     // int *型返回值
int getData( ... ) { ... }     // int型返回值

        这个函数可能是通过计算进而得到某个结果进行返回,可能是从某个地方获取一个值把它返回,前者可能会发生计算错误,后者可能发生取值错误(这个可能性就比较高了),比如指定地址的值不存在等等。无论如何,函数需要把操作结果告诉用户(函数的使用者),即用户需要通过某种途径知道此函数是否成功执行了 。

       若返回值是char类型,可以返回‘\0’来表示“失败”(其实也不见得是个好的方式),因为有效字符不会是‘\0’值;
       若返回值是指针类型,可以返回NULL来表示“失败”,因为有效指针不会是NULL;

        但若返回类型是int类型呢?该返回什么值呢?事实上,对于int类型来说,没有任何值是无效的。但总要解决这个问题啊!

        这时异常就可以解决这个问题。对于带有返回值的函数,结束函数的运行,或者通过return语句结束函数,或者通过以抛出异常结束函数。 

        所以 ,上述int型返回值的getData函数可以如下这么写:

int getData()  
{  
     bool state = false;  
     int result = 0;     // as a return value  
     // do something  
     state = do_something(...);  
     if (!state)  
     {  
          throw someExceptionObject;     // someExceptionObject表示某个异常对象  
     }  
     return result;  
}  

        然后在调用这个getData()的程序段里可以这么处理

{  
     try  
     {  
          int result = getData();  
     }  
     catch (someExceptionClass & e)     // someException表示某个异常类  
     {  
          // error;  
     }  
}  

      这里插一个概念 -- 用户:比如在A函数中调用B函数,对于B函数来说,A就是用户。
      总之,在C中很多场合,函数可以执行“return NULL;”告诉它的用户“得不到正常的结果”,但有些场合“return xxoo;”无法解决问题,比如上述“int getData(...) { ... }”,在C++中,异常可以解决这个问题,不过用的不是return语句,而是throw语句;用户也不是通过“if (result == NULL)”之类的语句进行判断,而是调用“try-catch”语句。
      但异常的作用显然不是这么简单 -- “告诉用户得不到正常的结果”。对于C语言,若执行如下语句:

     int *p = new int;
     delete p;
     delete p;

       程序只能崩溃,而C++若使用合适的异常处理这个问题,可以避免程序崩溃,反正异常机制能干这么一种事情,关于如何实现,比较简单,但不是本文的重点,这里引出这个东东只是为了说明异常机制的重要性。

上面都是小case,开始一些稍微有意思的东西吧!
从问题开始:

1. 异常的本质是啥?有啥意义?

异常的本质:内容上文所提到的异常,比如bad_alloc其实是一个类,所以使用throw抛出异常时不能直接执行“throw bad_alloc;”这样的语句,而是先要创建一个bad_alloc对象,所以至少得这样“throw bad_alloc()”;

异常的意义:首先得明白一点,和NULL有一丁点类似,异常是一种通知用户“程序没能正常执行”或“得不到想要的结果”之类的机制,有点类似于函数和它的用户之间的一个约定!

异常机制能修复程序出现的问题吗?No,异常其实是一个非常非常简单的类,它不会对程序做任何实质性的改变,笔者认为从某种角度可以把异常看做C++定义的一种类似于NULL但比NULL要丰富得多的MARK,比如执行new语句失败了,系统会throw一个bad_alloc,用户(调用该new语句的程序)可以根据此错误做些其他事情(比如再执行一次之类的),总之,如何处理这些MAKR,全看用户的catch语句如何处理。

2. 如何抛出异常?

异常对象可能在程序执行期间产生异常时被系统抛出,也可能是某些程序显式执行“throw xxoo;”语句抛出给用户。

P.S:笔者对此并不确定,貌似Java中虚拟机可以抛出异常(可以认为是所谓的“系统”吧),但对于C++,笔者不太确定!或许以后会搞清楚!

3. 异常可以处理所有错误吗?

No,如下:

int main(int argc, char **argv)  
{  
    string *str = new string;  
    try      
    {             
        delete str;           
        delete str;                   
    }                                     
    catch (...)                               
    {                                             
    }     
    cout << "sb" << endl;                                     
  
  
    return 0;                                                     
}  

编译执行此程序仍然会发生错误,“sb”仍然无法被打印出来。

4. 关于自定义异常

当然可以自定义异常,public继承exception就可以了。
但是尽量不要自定义异常,《C++标准程序库》是这么说的:
使用非标准类别作为异常将导致程序难以移植,xxoo,所以最好使用标准异常。

但笔者认为,有时候自定义异常还是挺必要的,比如,上文所提到的“int getData(...) { ... }”,若得不到想要的结果时,return语句显然不满足,只能使用throw,但是throw已知的标准异常类对象是不太合适的,因为它们都有特别的意义,使用它们会给用户带来困惑,所以笔者认为此时需要自定义异常。

所有标准异常类的接口只含一个成员函数:what(),用以获取“type本身以外的附加信息”。它返回一个以null结束的字符串:

  1. namespace std{  
        class exception {  
            public:     
                virtual const char * what() const throw();  
                ...                         
        };      
    }  


5. 异常规格(exception specification)

函数可以随意返回各种异常吗?No,函数若想返回某个异常,需要在其声明式和定义式中添加这么一个东西(见蓝色部分)

void f() throw (bad_alloc, bad_cast);
void f() throw (bad_alloc, bad_cast)
{
     throw bad_cast();
     throw bad_alloc();
}

所谓异常规格,用来指明某个函数可能抛出哪些异常。

若函数抛出了没在异常规格中出现的异常会怎如何呢?
《C++标准程序库》中这么说:唤醒unexpected(),后者会唤醒terminated()终止程序。

所以,如下程序:

void f() throw (bad_alloc)  
{  
    throw bad_cast();  
    throw bad_alloc();  
}  
  
  
int main(int argc, char **argv)  
{  
    try   
    {     
        f();  
    }     
    catch(const bad_cast& e)  
    {     
  
  
    }     
    catch(const bad_alloc& e)  
    {  
  
  
    }  
    cout << "hello, world" << endl;  
}  

执行结果如下:

libc++abi.dylib: terminating with unexpected exception of type std::bad_cast: std::bad_cast 

[1]    3932 abort      ./a.out


“hello, world”还没来得及打印就退出了!

然而如果在异常规格中列出bad_exception,那么unexpected()总是会重新抛出(rethrows)bad_exception。

  1. class E1;   
    class E2;   // not derived from E1  
      
      
    void f() throw(E1, std::bad_exception)  
                    // throws exception of type E1 or  
                    // bad_exception for any other exception type  
    {  
        ...   
        throw E1(); // throws exception of type E1  
        ...   
        throw E2(); // throws unexpected(), which throws bad_exception  
    }  

因此,如果异常规格罗列了bad_exception,那么任何未列于规格的异常,都将在函数unexpected()中被代之以bad_exception。

 

7.异常的处理机制(补充)

  1. 若有异常则通过throw操作创建一个异常对象并抛出。
  2. 将可能抛出异常的程序段放在try块之中。控制通过正常顺序执行到达try块,然后执行try子块内的保护段。
  3. 如果在保护段执行期间没有引发异常,那么跟在try子块后的catch字句就不执行。程序继续执行紧跟try块最后一个catch子句后面的语句。
  4. catch子句按其在try块后出现的顺序被检查。类型匹配的catch子句将被捕获并处理异常(或重抛出异常)
  5. 如果找不到匹配的处理代码,则自动调用terminate,默认为abort()终止程序。

void f() throw (bad_alloc, bad_cast);
void f() throw (bad_alloc, bad_cast)
{
     throw bad_cast();
     throw bad_alloc();
}

posted @ 2017-12-07 20:06  拂石  阅读(254)  评论(0编辑  收藏  举报