C++之异常处理

异常是程序执行过程中发生的错误,C++的异常提供了检测和处理这种错误的手段,由三个关键字try,catch,throw来完成异常处理。


基本语法

抛出异常

使用throw关键字,当程序出现问题时抛出一个异常。使用throw语句在代码块中的任何地方抛出异常,throw语句的操作可以是任意的表达式,表达式的类型决定了抛出的异常类型。

1
2
3
4
5
6
7
double division(int a, int b) {
if (b == 0) {
throw "Division by zero condition!";
}
return (a/b);
}


捕获异常

可以通过catch语句来捕获异常,try语句用来激活特定的异常,后面跟多个catch块构成了异常处理。

1
2
3
4
5
try {
// 保护代码
} catch(ExcetionName e) {
// 异常处理
}

先来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
double division(int a, int b) {
if (b == 0) {
throw "Division by zero condition!";
}
return (a/b);
}
int main() {
try {
division(3, 0);
} catch (const char *error) {
cout << error << endl;
} catch (...) {
cout << "Unknown exception happened!" << endl;
}
return 0;
}

我们可以控制函数抛出的异常。

1
void f(int a) throw(e1, e2)

表示函数f只能抛出e1,e2,或者它们派生的异常,不会抛出其他异常,如果函数f()违反了这个规定,此时转换成调用unexcepted(),默认调用terminate(),通常会调用abort。

1
2
void f(int a); // 表示可能会抛出任何异常
void f(int a) thow(); // 不会抛出任何异常

使用catch(…)用来匹配任何异常。

1
2
3
4
5
try {
// 保护代码
} catch(...) {
cout << "Unknown exception happened!" << endl;
}

说明:

1、thorw可以抛出任意类型的变量(或表达式),catch按照抛出的变量编译时类型进行匹配,找到第一个匹配的类型即进入异常处理。
2、如果抛出的异常没有被处理,默认会调用terminate()函数终止整个程序。
3、异常匹配时、只允许三种类型转换:const与非const,派生类与基类,数组与指针。(不允许隐式或显示类型转换)
4、throw抛出的对象称之为异常对象,由编译器管理,catch接受到的对象不是引用或者指针的话,要进行对象拷贝,所以不允许传入不允许拷贝的异常对象。
5、构造函数可以抛出异常,可以在构造函数中或者构造函数初始化式中。如果构造函数发生异常,对象的析构函数将不会调用,所以需要在构造函数进行try-catch释放自己申请的资源,语法如下:

1
Constuctor() try: m_A(), m_B() { } catch(...) {}

6、析构函数不应该抛出异常,如果析构函数在执行过程中可能抛出异常,就应该在析构函数内部将这个异常处理进行处理,而不是将异常抛出。
7、catch可以将这个异常重新抛出进行处理。
8、异常处理中尽可能使用智能指针,异常发生时,会自动销毁资源。


C++标准异常

C++提供了一系列的标准异常,定义在头文件中,它们是以父子类层次结构组织起来的,如下图所示:
C++标准异常
下表是对上图层次结构中的每个异常的说明:

异常 描述
excetion 该异常是所有标准 C++ 异常的父类
bad_alloc 该异常可以通过 new 抛出
bad_cast 该异常可以通过 dynamic_cast 抛出
bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用
bad_typeid 该异常可以通过 typeid 抛出
logic_error 理论上可以通过读取代码来检测到的异常
domain_error 当使用了一个无效的数学域时,会抛出该异常
invalid_argument 当使用了无效的参数时,会抛出该异常
length_error 当创建了太长的 std::string 时,会抛出该异常
out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
runtime_error 理论上不可以通过读取代码来检测到的异常
overflow_error 当发生数学上溢时,会抛出该异常
range_error 当尝试存储超出范围的值时,会抛出该异常
underflow_error 当发生数学下溢时,会抛出该异常

exception定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class excetion {
public:
exception();
exception(const char *const &);
exception(const excetion&);
exception& operation= (const exception&);
virtual ~exception();
virtual char* what() const;
private:
const char *_m_what;
int _m_doFree;
}

定义新的异常类,我们可以通过继承和重载exception类来定义新的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
class MyException : public exception {
public:
const char* what() const throw() {
return "my exception";
}
};
int main() {
try {
MyException e;
throw e;
} catch (MyException &e) {
cout << e.what() << endl;
} catch (...) {
cout << "Unknown exception happened!" << endl;
}
return 0;
}


参考资料:
C++ 异常处理
C++ 异常处理