在C语言里,我们对可能出现的错误片段都会做相应处理,要么是用if,else语句分类判断,要么就直接使用assert进行报错处理。C++是OOP语言,C++11引入了异常,对可能出错的地方可以进行异常处理,抛出错误对象。以便工作人员快速找出错误,也防止程序因错误而突然终止。
异常的使用很简单
void func()
{
int x, y;
cin >> x >> y;
if(y)
cout << (double)x / (double)y << endl;
else
throw "错误!被除数为零";
}
int main()
{
try
{
func();
}
catch (const char* msg)
{
cout << msg << endl;
}
catch (const string& msg)
{
cout << msg << endl;
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
return 0;
}
try{ } ---------------- 里面放可能出错的语句
catch{ } ------------ 里面编写收到异常对象后的处理方式(注:抛出的异常对象只会对应一个catch)
const std::exception& e ------------ 表示接受抛出异常对象的类型
throw --------------- 表示抛出异常对象
当输入的y为0时,此时就会抛出异常对象给catch(const char* msg)
捕捉,因为抛出的是字符串数组。
异常的抛出和匹配原则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- catch(…) 可以捕获任意类型的异常,问题是不知道异常错误是什么。
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)。
- catch建议一般用父类类型异常对象接收,这样可以运用多态的特性,来实现不同的效果。
在函数调用链中异常栈展开匹配原则
如上图,如果在func2中设有catch,但又不想捕获,更希望func3来捕获处理,那么就在func2中再次throw异常,在func3中设catch来捕获。如下示例图:
- 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
- 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
- C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。
这是库里面的异常类(exception类),可见在面对我们开发的需求时,库里的异常类往往不太能满足我们的需要,所以我们会经常自定义异常类,来继承库里的异常类,或者不继承。