zoukankan      html  css  js  c++  java
  • C++中的异常

      我们先看一下异常跟断言的区别: 

      “ 

      异常被捕获后可以不作处理,程序从捕获位置继续执行。而断言是完全无法忽略的,程序在断言失败处立即终止。因此断言通常用于调试版本,用来发现程序中的逻辑错误。虽然异常也能起到这样的作用,但是不应该用异常代替断言:
      1) 如果发现了逻辑错误,必须修改程序,而不可能在程序中进行处理和恢复,所以不需要向外传送,没有必要使用异常。
      2) 使用断言的开销比异常小得多,而且断言可以从发布版中完全去除。

      异常用于处理正确程序中的运行期问题(比如内存分配失败,窗口创建失败,线程创建失败,打开文件失败),以尽可能恢复,而不是终止程序。对于运行异常,使用断言是非常不合适的,理由很显然:
      1) 断言在发布版不起作用;
      2) 断言的处理方式不够友好;
      3) 运行异常不是程序错误,没有必要报告源代码出错位置。

      ” 

    一个简单例子及catch(...)的作用

     1 #include <iostream>
     2 #include <stdlib.h>
     3 
     4 using namespace std;
     5 
     6 double func(double x, double y)
     7 {
     8     if (y == 0)
     9     {
    10         throw y;        // 抛出异常
    11     }
    12     return x / y;
    13 }
    14 
    15 int main()
    16 {
    17     double res;
    18     try
    19     {
    20         res = func(2, 3);
    21         cout << "The result of x/y is : " << res << endl;
    22         res = func(4, 0);
    23     }
    24     catch (double)        // 捕获异常
    25     {
    26         cerr << "error of dividing zero.
    " << endl;;
    27         exit(1);
    28     }
    29     catch (...)            // 类似于switch case语句中会用到的的default语句
    30     {
    31         cerr << "exception occurs" << endl;
    32     }
    33 
    34     return 0;
    35 }
    A simple example

      catch(…)能够捕获多种数据类型的异常对象,所以它提供给程序员一种对异常对象更好的控制手段,使开发的软件系统有很好的可靠性。因此一个比较有经验的程序员通常会这样组织编写它的代码模块,如下:

     1 void Func()
     2 {
     3     try
     4     {
     5         // 这里的程序代码完成真正复杂的计算工作,这些代码在执行过程中
     6         // 有可能抛出DataType1、DataType2和DataType3类型的异常对象。
     7     }
     8     catch (DataType1& d1)
     9     {
    10     }
    11     catch (DataType2& d2)
    12     {
    13     }
    14     catch (DataType3& d3)
    15     {
    16     }
    17     // 注意上面try block中可能抛出的DataType1、DataType2和DataType3三
    18     // 种类型的异常对象在前面都已经有对应的catch block来处理。但为什么
    19     // 还要在最后再定义一个catch(…) block呢?这就是为了有更好的安全性和
    20     // 可靠性,避免上面的try block抛出了其它未考虑到的异常对象时导致的程
    21     // 序出现意外崩溃的严重后果,而且这在用VC开发的系统上更特别有效,因
    22     // 为catch(…)能捕获系统出现的异常,而系统异常往往令程序员头痛了,现
    23     // 在系统一般都比较复杂,而且由很多人共同开发,一不小心就会导致一个
    24     // 指针变量指向了其它非法区域,结果意外灾难不幸发生了。catch(…)为这种
    25     // 潜在的隐患提供了一种有效的补救措施。
    26     catch (…)
    27     {
    28     }
    29 }

    异常中采用面向对象的处理

      先看个例子

     1 #include <iostream>
     2 #include <exception>
     3 
     4 using namespace std;
     5 
     6 class ExceptionClass
     7 {
     8 public:
     9 
    10     ExceptionClass(char * name = "Exception Default Class")
    11     {
    12         cout << "Exception Class : Construct String" << endl;
    13     }
    14 
    15     virtual ~ExceptionClass()
    16     {
    17         cout << "Exception Class : Destruct String" << endl;
    18     }
    19 
    20     void ReportError()
    21     {
    22         cout << "Exception Class : Report Error Message" << endl;
    23     }
    24 
    25 };
    26 
    27 class TestedClass
    28 {
    29 public:
    30 
    31     TestedClass(char * name = "dufault name")
    32     {
    33         cout << "Construct String::" << name << endl;
    34         this->name = name;
    35     }
    36 
    37     virtual ~TestedClass()
    38     {
    39         cout << "Destruct String" << endl;
    40     }
    41 
    42     void mythrow()
    43     {
    44         throw ExceptionClass("my throw");
    45     }
    46 
    47 private:
    48     char * name;
    49 
    50 };
    51 
    52 int main()
    53 {
    54     TestedClass e("Test");
    55     try
    56     {
    57         e.mythrow();
    58     }
    59     catch (ExceptionClass eTestedClass)
    60     {
    61         eTestedClass.ReportError();
    62     }
    63     catch (...)
    64     {
    65         cout << "*******************" << endl;
    66     }
    67 
    68     return 0;
    69 }
    View code

      在该例子中专门设计了一个异常类来处理异常。

      在博文“C++的try_catch异常”中,作者还提供了另一个很值得参考的例子。

     1 void OpenFile(string f)
     2 {
     3     try
     4     {
     5         // 打开文件的操作,可能抛出FileOpenException
     6     }
     7     catch (FileOpenException& fe)
     8     {
     9         // 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
    10         // 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
    11         // 理这个异常对象
    12         int result = ReOpenFile(f);
    13         if (result == false) throw;
    14     }
    15 }
    16 
    17 void ReadFile(File f)
    18 {
    19     try
    20     {
    21         // 从文件中读数据,可能抛出FileReadException
    22     }
    23     catch (FileReadException& fe)
    24     {
    25         // 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
    26         // 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处
    27         // 理这个异常对象
    28         int result = ReReadFile(f);
    29         if (result == false) throw;
    30     }
    31 }
    32 
    33 void WriteFile(File f)
    34 {
    35     try
    36     {
    37         // 往文件中写数据,可能抛出FileWriteException
    38     }
    39     catch (FileWriteException& fe)
    40     {
    41         // 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
    42         // 正常返回;否则必须重新抛出这个异常,以供上层的调用函数来能再次处理这个异常对象
    43         int result = ReWriteFile(f);
    44         if (result == false) throw;
    45     }
    46 }
    47 
    48 void Func()
    49 {
    50     try
    51     {
    52         // 对文件进行操作,可能出现FileWriteException、FileWriteException
    53         // 和FileWriteException异常
    54         OpenFile(…);
    55         ReadFile(…);
    56         WriteFile(…);
    57     }
    58     // 注意:FileException是FileOpenException、FileReadException和FileWriteException
    59     // 的基类,因此这里定义的catch(FileException& fe)能捕获所有与文件操作失败的异
    60     // 常。
    61     catch (FileException& fe)
    62     {
    63         ExceptionInfo* ef = fe.GetExceptionInfo();
    64         cout << “操作文件时出现了不可恢复的错误,原因是:” << fe << endl;
    65     }
    66 }
    View Code

    构造和析构函数中的异常抛出

      先看个例子:

     1 #include <iostream>
     2 #include <stdlib.h>
     3 
     4 using namespace std;
     5 
     6 class ExceptionClass
     7 {
     8 public:
     9 
    10     ExceptionClass()
    11     {
    12         cout << "Construct." << endl;
    13         s = new char[4];
    14         cout << "Throw a exception." << endl;
    15         throw 18;
    16     }
    17     ~ExceptionClass()
    18     {
    19         cout << "Destruct." << endl;
    20         delete[] s;
    21     }
    22 
    23 private:
    24 
    25     char* s;
    26 
    27 };
    28 
    29 void main()
    30 {
    31     try
    32     {
    33         ExceptionClass e;
    34     }
    35     catch (...)
    36     {
    37     }
    38 }
    View Code

      程序运行结果为:

      Construct.

      Throw a exception.

      在这两句输出之间,我们已经给 s 分配了内存,但内存没有被释放(因为它是在析构函数中释放的)。应该说这符合实际现象,因为对象没有完整构造。

      为了避免这种情况,我想你也许会说:应避免对象通过本身的构造函数涉及到异常抛出。即:既不在构造函数中出现异常抛出,也不应在构造函数调用的一切东西中出现异常抛出。但是在C++中可以在构造函数中抛出异常,经典的解决方案是使用STL的标准类unique_ptr

      那么,在析构函数中的情况呢?我们已经知道,异常抛出之后,就要调用本身的析构函数,如果这析构函数中还有异常抛出的话,则已存在的异常尚未被捕获,会导致异常捕捉不到。

    标准C++异常类

      标准异常都派生自一个公共的基类exception。基类包含必要的多态性函数提供异常描述,可以被重载。下面是exception类的原型:

    1 class exception 
    2 {
    3 public:
    4     exception() throw();
    5     exception(const exception&) throw();
    6     exception& operator= (const exception&) throw();
    7     virtual ~exception() throw();
    8     virtual const char* what() const throw();
    9 };

      其他派生自基类exception的标准异常类为:

     1 namespace std
     2 {
     3     //exception派生
     4     class logic_error;         //逻辑错误,在程序运行前可以检测出来
     5 
     6     //logic_error派生
     7     class domain_error;        //违反了前置条件
     8     class invalid_argument;   //指出函数的一个无效参数
     9     class length_error;        //指出有一个超过类型size_t的最大可表现值长度的对象的企图
    10     class out_of_range;        //参数越界
    11     class bad_cast;            //在运行时类型识别中有一个无效的dynamic_cast表达式
    12     class bad_typeid;         //报告在表达试typeid(*p)中有一个空指针p
    13 
    14     //exception派生
    15     class runtime_error;      //运行时错误,仅在程序运行中检测到
    16 
    17     //runtime_error派生
    18     class range_error;         //违反后置条件
    19     class overflow_error;      //报告一个算术溢出
    20     class bad_alloc;           //存储分配错误
    21 
    22 }
      一个利用标准异常类的例子如下:
     1 #include <iostream>
     2 #include <exception>
     3 
     4 using namespace std;
     5 
     6 class TestedClass
     7 {
     8 public:
     9 
    10     TestedClass(char * name = "dufault name")
    11     {
    12         cout << "Construct String::" << name << endl;
    13         this->name = name;
    14     }
    15 
    16     virtual ~TestedClass()
    17     {
    18         cout << "Destruct String" << endl;
    19     }
    20 
    21     void mythrow()
    22     {
    23         throw logic_error("my throw");
    24     }
    25 
    26 private:
    27     char * name;
    28 
    29 };
    30 
    31 int main()
    32 {
    33     TestedClass e("Test");
    34     try
    35     {
    36         e.mythrow();
    37     }
    38     catch (logic_error& e)
    39     {
    40         cerr << "logic error exception caught: " << e.what() << endl;
    41     }
    42     catch (exception& e)
    43     {
    44         cerr << "exception caught!" << e.what() << endl;
    45     }
    46     catch (...)
    47     {
    48         cout << "*******************" << endl;
    49     }
    50 
    51     return 0;
    52 }
    View Code

    参考资料

      C++的try_catch异常

  • 相关阅读:
    How do I access arcobjects from python?
    Win7 打开或关闭Windows功能 窗口空白 解决方案(ZZ)
    解释什么叫工作
    电脑城奸商最怕顾客知道的十条经验
    25岁前你要学会放下的八样东西
    必看十大电影
    SQL Server 中查询非中文,非英文,非数字的特殊列
    CHARINDEX 和 PATINDEX
    主流开源数据库的技术特点点评
    information_schema.routines与sysobjects
  • 原文地址:https://www.cnblogs.com/xiehongfeng100/p/4029573.html
Copyright © 2011-2022 走看看