首页 > 代码库 > C++学习笔记(十五):异常

C++学习笔记(十五):异常

C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。
Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。
也就是《C++ primer》中说的:将问题检测和问题处理相分离。

C++中使用异常时应注意的问题:

  1. 性能问题。这个一般不会成为瓶颈,但是如果你编写的是高性能或者实时性要求比较强的软件,就需要考虑了。
  2. 指针和动态分配导致的内存回收问题:在C++中,不会自动回收动态分配的内存,如果遇到异常就需要考虑是否正确的回收了内存。在java中,就基本不需要考虑这个,有垃圾回收机制真好!
  3. 函数的异常抛出列表:java中是如果一个函数没有在异常抛出列表中显式指定要抛出的异常,就不允许抛出;可是在C++中是如果你没有在函数的异常抛出列表指定要抛出的异常,意味着你可以抛出任何异常。
  4. C++中编译时不会检查函数的异常抛出列表。这意味着你在编写C++程序时,如果在函数中抛出了没有在异常抛出列表中声明的异常,编译时是不会报错的。而在java中,eclipse的提示功能真的好强大啊!
  5. 在java中,抛出的异常都要是一个异常类;但是在C++中,你可以抛出任何类型,你甚至可以抛出一个整型。(当然,在C++中如果你catch中接收时使用的是对象,而不是引用的话,那么你抛出的对象必须要是能够复制的。这是语言的要求,不是异常处理的要求)。
  6. 在C++中是没有finally关键字的。而java和python中都是有finally关键字的。

如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。
由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。

 1 throw 表达式; 2  3 try  4 {   5         包含可能抛出异常的语句;   6 }   7 catch(类型名 [形参名]) // 捕获特定类型的异常   8 {   9  10 }  11 catch(类型名 [形参名]) // 捕获特定类型的异常  12 {  13  14 }  15 catch(...)    // 三个点则表示捕获所有类型的异常  16 {  17 } 

示例:

 1 #include<iostream.h>     //包含头文件   2 #include<stdlib.h>   3   4 double fuc(double x, double y) //定义函数   5 {   6     if(y==0)   7     {   8         throw y;     //除数为0,抛出异常   9     }  10     return x/y;     //否则返回两个数的商  11 }  12  13 void main()  14 {  15     double res;  16     try  //定义异常  17     {  18         res=fuc(2,3);  19         cout<<"The result of x/y is : "<<res<<endl;  20         res=fuc(4,0); 出现异常,函数内部会抛出异常  21     }  22     catch(double)             //捕获并处理异常  23     {  24          cerr<<"error of dividing zero.\n";  25          exit(1);                //异常退出程序  26     }  27 } 

标准库中的异常类:
和java一样,标准库中也提供了很多的异常类,它们是通过类继承组织起来的。标准异常被组织成八个。
异常类继承层级结构图如下:

标准异常类的成员:
在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。
logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述;
所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。

标准异常类的具体描述:
exception 所有标准异常类的父类
bad_alloc 当operator new and operator new[],请求分配内存失败时
bad_exception 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数中若抛出异常,不论什么类型,都会被替换为bad_exception类型
bad_typeid 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常
bad_cast 使用dynamic_cast转换引用失败的时候
ios_base::failure io操作过程出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误

logic_error的子类:
length_error 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作
domain_error 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常

runtime_error的子类:
range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢

 

编写自己的异常类:

  1. 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
  2. 当继承标准异常类时,应该重载父类的what函数和虚析构函数。
  3. 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数。

 

C++学习笔记(十五):异常