目录
一、c语言传统的处理错误方式
1、终止程序,如assert断言。
2、返回错误码:返回一个编号,这个编号对应某个错误,需要你自己去查。
二、c++异常
异常是一种处理问题的方式,当一个函数发现自己无法处理的错误时,就可以抛出异常,告诉你错了什么,错在哪里,然后让函数的直接/间接调用者来处理这个错误。
为了实现异常,需要增加三个关键字:throw,catch,try.
throw:当出现异常时,程序会抛出一个异常。(本质是抛出一个对象,可以抛出任意类型的对象,例如char、int或者自定义类型均可。)
如果有一个块抛出异常,捕获异常的方法会用到try和catch关键字。
try:内部放置可能会出现异常的代码,try块中的代码被称为保护代码。
catch:捕获异常,内置对异常的处理代码。
语法使用格式如下:
try
{ // 保护代码 (有可能会出错的代码)}
catch( ExceptionName e1 )
{ // catch 块(处理异常的代码) }
catch( ExceptionName e2 )
{ // catch 块 }
catch( ExceptionName eN )
{ // catch 块 }
一个try块内可能会抛出多个异常,就需要多个catch来捕获
每一个catch匹配一个类型,catch捕获的机制就是类型匹配。
try如果没有异常发生,就会直接跳过catch
三、异常的抛出和匹配原则
1、异常通常是由抛出对象引发的,该对象的类型决定了应该激活哪个catch的处理代码
2、被选中的处理代码,是调用链中于该对对象类型匹配且离抛出异常位置最近的。
也就是说即使有多个catch匹配,但是匹配一个,离抛出最近的那个。
3、抛出异常的对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,
多以会产生一个拷贝对象,这个拷贝的临时对象会被在catch后销毁。
4、catch(...)可以捕获任何类型的异常,只是不知道异常的错误具体是什么
5、实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配的,可以抛出的派生类对象。
四、在函数调用链中异常栈展开匹配原则
1、首先检查throw本身是否在try块内部,(我自己抛,我就在原地处理)如果是,则在再检查匹配的catch。如果有匹配的catch,则到调用catch的地方处理
2、如果没有匹配,则退出当前栈,继续在调用栈中进行查找匹配的catch
3、如果到了main函数的栈帧,依旧没有匹配的catch,就会终止程序。
上述沿着调用链查找匹配catch的过程成为栈展开。
所以,我们在main函数最后都要加一个catch(...)来捕获任意异常类型,否则没有捕获到异常,程序将会终止。
因为异常必须被处理。否则程序就会被直接终止。
一般异常都是在外层去捕获。
4、找到匹配的catch处理异常后,会继续沿着catch语句后面的代码继续执行。
也就是异常捕获成功后,后面的程序正常执行。
五、实践运用
但是上述的捕获异常的方式,比较麻烦。
为什么?
因为一个项目的运行,可能会抛出各种类型的异常,
这就导致我们的外层程序员手忙脚乱,冷不丁就会那里出异常。
而且即使是catch(...)这种方式,抛出的是未知异常,无法彻底解决。
那么,到底怎么办呢?
可以抛出派生类对象,捕获基类对象。(实践当中非常好用)
抛出的都是派生类,这里就使用了多态的特性
1、虚函数重写
2、父类/基类的指针/引用调用
抛出那个派生类的异常就会调用那个的虚函数。
//这种格式代表:捕获什么,抛出什么
catch(...)
{
throw ;
}
不要在构造函数和析构函数中抛出异常:
有可能回导致某些位置没有被初始化或者
某些位置没有被析构
六、异常规范
为了让函数使用者知道该函数可能回抛出的异常有那些:
1、可以在函数后面接throw(类型),括号中列出可能抛出的所有异常类型。
2、函数的后面接throw(),表示不抛出异常
3、若没有异常接口声明,该函数可能回抛出任何类型的异常。
c++新增规范:noexcept
在函数后面加上noexcept表示不会抛出异常。
没有写,有可能回抛出异常
七、异常优点
1、清晰准确的描述错误的信息,帮助更好的定位程序的bug
2、传统返回错误代码编号,需要到层层返回,到代码的最外层才能获取错误,
但是异常捕获直接就跳到catch位置返回和处理。
3、更好的处理一些函数错误。例如越界,传统只能终止程序,而且无法返回错误编码。
异常就可以很好的处理
八、异常缺点
1、程序执行流乱跳,比较混乱,难以追踪调试
2、额外性能开销(但基本忽略不计)
3、c++没有垃圾回收机制,资源自己管理,异常很容易导致内存泄露,死锁等问题
4、c++标准库的异常体系不太好,导致大家各自定义各自的异常体系,混乱。
5、有了异常以后,会导致异常使用不规范,程序的总体管理、运维、沟通成本上升。
九、常见异常类型
异常类 | 描述 |
---|---|
std::exception |
所有标准异常的基类。 |
std::bad_alloc |
在动态分配内存时,当无法分配所需内存时抛出。 |
std::bad_cast |
在 dynamic_cast 运算中,转换目标类型不合法时抛出。 |
std::bad_exception |
用于处理未被 throw 表达式捕获的异常类型。 |
std::bad_typeid |
在 typeid 运算无法获取到有效的类型信息时抛出。 |
std::logic_error |
非临界性的逻辑错误,可以通过修改程序来避免。 |
std::domain_error |
当参数超出有效域时抛出,例如在数学函数中使用了无效的参数。 |
std::invalid_argument |
当提供的参数值不适合操作时抛出。 |
std::length_error |
当试图创建一个超出该容器最大长度的对象时抛出。 |
std::out_of_range |
当访问超出有效范围的对象时抛出,例如数组越界访问。 |
std::runtime_error |
用于表示可以通过处理来避免的运行时错误。 |
std::overflow_error |
当数值运算超出数值类型可以表示的范围时抛出。 |
std::underflow_error |
当数值运算结果太小无法表示时抛出。 |