文章目录
- 🍖异常是什么
- 🌭概念
- 🌭实现方式
- 🍖异常的使用和注意事项
- 🌭注意事项
- 🌭异常的重新抛出
- 🌭异常安全
- 🍖异常的规范
- 🍖异常带来的优缺点
🍖异常是什么
🌭概念
在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建议一般用父类类型异常对象接收,这样可以运用多态的特性,来实现不同的效果。
在函数调用链中异常栈展开匹配原则
- 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
- 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
- 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
- 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
🌭异常的重新抛出
如上图,如果在func2中设有catch,但又不想捕获,更希望func3来捕获处理,那么就在func2中再次throw异常,在func3中设catch来捕获。如下示例图:
🌭异常安全
- 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
- 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
- C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。
🍖异常的规范
这是库里面的异常类(exception类),可见在面对我们开发的需求时,库里的异常类往往不太能满足我们的需要,所以我们会经常自定义异常类,来继承库里的异常类,或者不继承。
🍖异常带来的优缺点
- 优点
- 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包
含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
- 缺点
- 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会
导致我们跟踪调试时以及分析程序时,比较困难。 - C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常
安全问题。