目录
一、C++ 异常处理的基本语法
1.1 - 抛出异常
1.2 - 检测和捕获异常
二、在函数调用链中异常栈展开的匹配原则
三、异常重新抛出
四、异常规范
五、C++ 标准异常体系
程序的错误大致可以分为以下三种:
-
语法错误:在编译和链接阶段就能发现,只有符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
-
逻辑错误:编写的代码不能够达到预期的效果,这种错误可以通过调试来解决。
-
运行时错误:指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组下标越界、文件不存在等,如果不能发现这些错误并加以处理,很可能会导致程序崩溃。C++ 异常机制就是为了解决运行时错误而引入的。
一、C++ 异常处理的基本语法
1.1 - 抛出异常
C++ 通过 throw 关键字抛出异常,语法为:
throw exceptionData;
exceptionData 可以是任意类型的异常数据。
示例:
int division(int a, int b)
{
if (b == 0)
throw "Division by zero condition!";
else
return a / b;
}
1.2 - 检测和捕获异常
检测和捕获异常的语法为:
try {
// 可能抛出异常的代码
}
catch (exceptionType1 e1) {
// 处理异常的代码
}
...
catch (exceptionTypeN eN) {
// 处理异常的代码
}
try...catch 语句的执行过程是:执行 try 块中的语句(称作检测异常),如果执行过程中没有异常抛出,那么所有 catch 块中的语句都不会被执行;如果执行过程中抛出了异常,那么抛出异常后会立即跳转到第一个异常类型和抛出的异常类型匹配的 catch 块,并执行其中的语句(称作捕获异常)。
注意:
catch 可以有多个,但至少要有一个。
catch(...) 可以捕获任意类型的异常。
可以使用基类捕获派生类对象。
如果不希望处理异常数据,可以将 e1、...、eN 省略掉。
示例:
#include <iostrea>
using namespace std;
class Base { };
class Derived : public Base { };
int main()
{
try {
throw Derived();
cout << "This statement will not be executed." << endl;
}
catch (Base) {
cout << "Exception type::Base" << endl;
}
catch (Derived) {
cout << "Exception type::Derived" << endl;
}
catch (...) {
cout << "Unknow exception" << endl;
}
// Exception type::Base
return 0;
}
二、在函数调用链中异常栈展开的匹配原则
-
当异常被抛出后,首先检查 throw 语句是否在 try 块内部,如果在,则查找匹配的 catch 块;
-
如果没找到匹配的 catch 块,则退出当前函数栈,继续在调用该函数的函数栈中查找匹配的 catch 块;
-
如果到达 main 函数栈,依旧没有找到匹配的 catch 块,则终止程序。
上述这个沿着函数调用链查找匹配的 catch 块的过程称为栈展开。
#include <iostream>
using namespace std;
int division(int a, int b)
{
if (b == 0)
throw "Division by zero condition!";
else
return a / b;
}
void func()
{
int a = 0, b = 0;
cin >> a >> b;
cout << division(a, b) << endl;
}
int main()
{
try {
func();
}
catch (const char* errmsg) {
cout << errmsg << endl;
}
catch (...) {
cout << "Unknow exception" << endl;
}
return 0;
}
三、异常重新抛出
单个 catch 块有可能不能完全处理一个异常,在进行一些校正处理后,希望将异常交给更外层的调用函数来处理,那么在 catch 块中就可以将异常重新抛出。
#include <iostream>
using namespace std;
int division(int a, int b)
{
if (b == 0)
throw "Division by zero condition!";
else
return a / b;
}
void func()
{
int* arr = new int[10];
try {
int a = 0, b = 0;
cin >> a >> b;
cout << division(a, b) << endl;
}
catch (...) {
delete[] arr;
cout << "func : delete[] finished" << endl;
throw; // 异常重新抛出
}
delete[] arr;
}
int main()
{
try {
func();
}
catch (const char* errmsg) {
cout << errmsg << endl;
}
catch (...) {
cout << "Unknow exception" << endl;
}
return 0;
}
throw;
没有指明抛出什么类型的异常,因此抛出的就是 catch 块捕获到的异常,这个异常会被 main 函数中的 catch 块捕获。
四、异常规范
throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范。
void func() throw(int);
这条语句声明了一个名为 func 函数,它的返回值类型为 void,参数列表为空,并且只能抛出 int 类型的异常,如果抛出其他类型的异常,try 将无法捕获,只能终止程序。
如果函数会抛出多种类型的异常,那么可以用逗号隔开。
void func() throw(int, char, double);
如果函数不会抛出任何任何异常,那么 () 中什么也不写。
void func() throw();
// C++ 中新增的 noexcept 关键字,表示不会抛出异常
void func() noexcept;
此时,func() 函数就不能抛出任何类型的异常了,即使抛出了,try 也检测不到。
·
五、C++ 标准异常体系
C++ 语言本身或者标准库中抛出的异常都是 exception 的子类,称为标准异常。
exception 类位于 <exception> 头文件中,它被声明为:
class exception {
public:
exception () throw();
exception (const exception&) throw();
exception& operator= (const exception&) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
}
下图展示了 exception 类的继承层次:
实际上,很多公司都会自定义自己的异常体系进行规范的异常管理。