目录
为什么要有异常:
异常的抛出和捕获:
异常在C++用于错误处理,C语言中一般使用返回值表示错误,C++对于错误处理进行了拓展,统一使用异常机制来处理程序中发生的错误
C++的异常处理包括两个部分----抛出异常和捕获异常,如果抛出的异常被捕获,处理完之后程序会继续运行,如果抛出的异常未被捕获,将导致程序终止
(1)抛出----throw
throw是用于抛出异常的关键字
throw 异常(对象)
在函数声明中应该指出可能抛出的异常
void show();//该函数可能抛出任何异常
void show()throw();//该函数不抛出任何异常
void show()throw(char, int);//可能会抛出char类型和int型的异常
没有捕获的话,发生异常就会直接把程序干死,报出异常错误,如下代码示例:
void fun()throw(int, double, const char*)
{
//可能会抛出int,double,const char*类型的错误
srand((unsigned int)time(NULL));
int s = rand() % 4;//0 1 2 3
cout << "fun start" << endl;
if (s == 0) {
cout << "正常运行!" << endl;
}
else if (s == 1) {
throw 1;//抛出int型异常
}
else if (s == 2) {
throw 1.235;//爬出double型
}
else if (s == 3) {
throw "错了错了";
}
cout << "fun over" << endl;
}
int main(int argc, const char** argv) {
fun();
return 0;
}
如果上面代码随机值取完rand后是1 2 3随便一个,他就会抛出异常,把程序干死,就像下面一样
运气好了取到0了那就正常退出咯
那么我们写程序不可能明知道可能会有异常,把错误专门扔出来放着,让他把程序干死,我们肯定是需要一些去处理这个被抛出异常的方法的。
(2)那么就需要进行异常的捕获--------try.....catch
try {
//可能抛出的异常的代码
}
catch (异常类型1 变量) {//根据类型捕获异常
//处理异常类型2的代码
//throw 变量----继续向上一层抛出
}
catch(...)//捕获任意类型的异常
{
}
...
//异常捕获后,执行对应的异常处理代码,然后继续执行try...catch之后的代码
//异常未被捕获,仍然导致程序终止
//捕获异常后,如果当前无法处理,可以继续抛出
void fun()throw(int, double, const char*)
{
//可能会抛出int,double,const char*类型的错误
srand((unsigned int)time(NULL));
int s = rand() % 4;//0 1 2 3
cout << "fun start" << endl;
if (s == 0) {
cout << "正常运行!" << endl;
}
else if (s == 1) {
throw 1;//抛出int型异常
}
else if (s == 2) {
throw 1.235;//爬出double型
}
else if (s == 3) {
throw "错了错了";
}
cout << "fun over" << endl;
}
int main(int argc, const char** argv) {
try {
//可能抛出的异常的代码
fun();
cout << "after fun!" << endl;
}
catch (int a) {//捕获int异常
cout << "捕获到了int类型异常=" << a << endl;
}
catch (double b) {
cout << "捕获到了double类型异常=" << b << endl;
}
catch (const char* p) {
cout << "捕获到了const char*类型异常=" << *p << endl;
cout << "无法处理!" << endl;
throw p;
//throw 变量----继续向上一层抛出
}
cout << "finished" << endl;
return 0;
}
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
就像上面我在处理const char*类型的时候,虽然捕获到了然后不能处理,throw扔出去,寻找外面能处理的函数去处理他,不过上面代码我就是简单的演示了一下扔出去,没有做处理
异常的抛出和匹配原则:
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
- catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么。
- 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用。
在函数调用链中异常栈展开匹配原则:
- 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
- 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
- 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
- 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
异常安全相关:
构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则**可能导致资源泄漏(**内存泄漏、句柄未关闭等)
C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。
实际开发中多鼓励使用异常,利大于弊!