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异常

原文地址:https://www.cnblogs.com/xiehongfeng100/p/4029573.html