C++中的异常
1、C语言中的异常处理办法
传统的异常处理方式:
1、终止程序,比如assert
2、返回错误码,需要程序员去查对应的错误码
3、C标准库中setjmp和longjmp组合
2、C++中的异常概念
异常是一种处理错误的方式,当函数发现自己无法处理的错误就可以抛出异常,让函数的调用者来处理这个错误
throw:用throw抛出异常
catch:用catch来捕获异常一个throw可以有多个catch进行捕获
try:try块中的代码标识将被激活的特定异常,后面跟着一个或多个catch块
注意:
1.try catch不分家,catch可以用…接收任意类型的异常.
2.throw出的错误可以在catch中捕获.进行对应处理,一般来说,throw出的变量的类型和catch出的类型必须完全匹配,子类的对象可以被父类的对象捕获.
3.在某个栈中抛出的错误可以由它的上一层栈(调用者)进行处理.
#include <iostream>
using namespace std;
int main()
{
try
{
//保护的标识代码
}catch (ExceptionName e1)
{
//catch块
}catch (ExceptionName e2)
{
//catch块
}catch (ExceptionName eN)
{
//catch块
}
system("pause");
return 0;
}
3、异常抛出和捕获
1、异常是通过抛出对象而引发,通过对象的类型来决定激活哪个catch
2、被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
3、抛出异常对象后,会生成一个异常对象拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁
4、catch()可以捕获任意类型的异常,问题是不知道异常错误是什么
5、实际中抛出和捕获的匹配原则有例外,并不是类型完全匹配,可以抛出的派生类对象,使用基类捕获。
函数调用链中异常栈展开匹配原则
1、首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句,如果有匹配的,则调到catch的地方进行处理
2、没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch
3、如果到达main函数的栈,依旧没有匹配的,则终止程序
4、找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
#include <iostream>
using namespace std;
double Division(int a, int b)
{
//当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
system("pause");
return 0;
}
4、异常的重新抛出
一个catch不能完全处理一个异常,在进行一些校正处理后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
#include <iostream>
using namespace std;
double Division(int a, int b)
{
//当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
else
{
return ((double)a / (double)b);
}
}
void Func()
{
//如果发生除0错误抛出异常
//array没有释放抛出异常
//捕获异常交给外面处理,这里捕获后重新抛出
int* array = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete[]" << array << endl;
delete[] array;
throw;
}
cout << "delete[]" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
system("pause");
return 0;
}
5、自定义异常和标准库异常
5.1、自定义异常
通过一套继承的规范体系,抛出派生类对象,捕获一个基类对象就可以
#include <iostream>
using namespace std;
class Exception
{
protected:
string m_errmsg;
int m_id;
};
class SqlException : public Exception
{
};
class CatchException : public Exception
{
};
class HttpServerException : public Exception
{
};
int main()
{
try
{
//抛出对象都是派生类对象
}
catch (const Exception& e)//这里只需要捕获基类异常就可以
{
}
catch (...)
{
cout << "Unknow Exception" << endl;
}
system("pause");
return 0;
}
5.2、标准库异常
C++t提供了一系列标准的异常,它们是以父子类层次结构组织起来的
C++标准库的异常类层次结构
#include <iostream>
#include <vector>
using namespace std;
int main()
{
try
{
vector<int> v(10, 5);
//系统内存不够报错
v.reserve(1000000);
//越界报错
v.at(10) = 100;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unknow Exception" << endl;
}
system("pause");
return 0;
}
6、异常安全和异常规范
6.1、异常安全
构造函数不要抛出异常,否则导致对象不完整或者没有完全初始化
析构函数不要抛出异常,否则导致资源泄漏
new和delete之间抛异常会导致内存泄漏,lock和unlock抛异常会导致死锁
6.2、异常规范
可以在函数后面接throw(类型)来列出这个函数可能抛出的所有异常
函数后面接throw()可以不抛异常
若无异常接口声明,则次函数可以抛任何类型异常
7、异常的优缺点
7.1、异常的优点
1、相比错误码,可以更清晰准确的展示出错误信息,帮助定位错误
2、第三方库包含异常,使用方便
3、更好的进行白盒测试
7.2、异常的缺点
1、异常会导致程序执行流乱跳,调试困难
2、异常会有性能开销
3、导致内存泄漏和死锁
4、异常体系混乱