目录
一.抛异常与运行崩溃的区别
1.运行崩溃
2.抛异常
二.抛异常机制存在的意义
1.清晰的处理错误
2.结构化的错误管理
3.跨函数传递错误信息
4.异常对象多态性
三.抛异常的使用方法
1.抛出异常 (throw)
2.捕获异常 (catch)
3.标准异常类
四.抛异常的处理机制
1.抛异常在函数调用链中的匹配规则
2.throw 与 catch 的细节问题
3.异常的重新抛出
一.抛异常与运行崩溃的区别
1.运行崩溃
运行崩溃是指程序在执行过程中由于某种无法恢复的错误而突然终止(这种情况是不受控的)。
原因:
①非法内存访问:尝试访问未分配或已释放的内存。
②空指针解引用:使用值为 nullptr 的指针。
③栈溢出:函数调用层次过深或局部变量占用过多栈空间。
④操作系统资源不足:如内存、文件句柄等耗尽。
⑤编译器或链接器生成的错误代码。
程序崩溃的结果:程序无法继续执行,通常会显示错误消息或生成核心转储文件。
2.抛异常
抛异常是程序中的一种受控的错误处理机制(即这种情况是我们已经提取预料到了的)。当程序遇到无法处理的错误或异常情况时,可以抛出一个异常对象,并跳到异常处理代码(即catch块)。
原因:
如除数为0、内存分配失败(new可能失败并抛出 std::bad_alloc 异常)、数组越界等。
抛异常的结果:如果异常被正确处理,程序可以继续执行。如果异常没有被捕捉,程序将调用terminate() 并终止。
小结
运行崩溃是程序由于无法恢复的错误而突然终止的情况。它通常是由于严重的运行时问题导致的。
抛异常是程序中的一种受控的错误处理机制。它允许程序在遇到错误时抛出异常对象,并将控制权转移到异常处理代码。如果异常被正确处理,程序可以继续执行;否则,程序将终止。
我们应该尽量避免运行崩溃,并通过抛异常和其他错误处理机制来优雅地处理潜在的错误情况。
二.抛异常机制存在的意义
1.清晰的处理错误
抛异常机制允许程序在运行时遇到错误或异常情况时,能够优雅地处理这些错误,而不是简单地崩溃或返回错误码。
2.结构化的错误管理
抛异常允许程序跳转到特定的错误处理代码块(即catch块),这使得错误恢复更加结构化和易于管理,程序可以在捕获异常后执行特定的清理操作,然后恢复执行。
3.跨函数传递错误信息
使用抛异常可以减少嵌套的错误检查代码,异常可以在函数之间传播,甚至可以在不同的模块或库之间传播。
4.异常对象多态性
异常对象可以是派生自std::exception的自定义异常类,这使得异常处理可以支持多态性。通过捕获基类异常(如std::exception),程序可以处理多种类型的异常,而无需为每个可能的异常类型编写单独的catch块。
三.抛异常的使用方法
1.抛出异常 (throw)
当程序检测到某种错误或异常情况时,我们可以使用 throw 关键字抛出一个异常,或者将可能出错的代码放在 try 块中,抛出的异常可以是任何类型的对象,但通常是继承自 std::exception 的标准异常类或用户自定义的异常类。
2.捕获异常 (catch)
使用 try 块包裹可能抛出异常的代码,然后在随后的 catch 块中捕获并处理这些异常,我们可以定义多个 catch 块来捕获不同类型的异常。
3.标准异常类
std::exception:所有标准异常的基类。
std::bad_alloc:内存分配失败时抛出。
std::bad_cast:在动态类型转换失败时抛出(例如,使用dynamic_cast)。
std::bad_exception:当无法处理一个通过throw抛出的异常时抛出(用于函数声明为throw(type))。
std::invalid_argument:传递给函数的参数无效时抛出。
std::length_error:试图创建一个超出允许长度的对象时抛出(例如,std::string)。
std::out_of_range:使用一个超出有效范围的值时抛出(例如,数组索引越界)。
std::overflow_error:算术运算结果上溢时抛出。
std::range_error:当函数结果超出其有效范围时抛出(不是标准异常类的一部分,但通常在标准库中与overflow_error和underflow_error一起讨论)。
std::runtime_error:在运行时发生异常时抛出(如运行时检测到的逻辑错误)。
std::underflow_error:算术运算结果下溢时抛出。
四.抛异常的处理机制
1.抛异常在函数调用链中的匹配规则
a.首先检查throw本身是否在try内部,如果不在,则程序立即终止(抛异常);如果在,就查找匹配的catch语句,如果当前栈帧有匹配的catch,则直接进入该catch内部。
b.如果当前栈帧内没有匹配的catch,则退出当前函数栈,回到上层函数栈中进行查找catch,若仍没有,则继续向上层栈帧去找。
c.如果到达main函数的栈,依旧没有找到catch,则终止程序,运行报错!
d.找到匹配的catch子句(catch参数类型匹配)并处理后,会继续沿着catch子句向后继续执行。
2.throw 与 catch 的细节问题
a.异常通过抛出对象而引发的,抛出对象的类型决定了应该匹配哪个catch的处理代码
b.throw所匹配的catch是函数调用链中与抛出对象类型匹配且离抛异常位置最近的那一个。
c.抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝出来的临时对象会在catch后销毁。
d.catch(. . .)可以捕获任意类型的异常,但就是不知道异常错误是什么,它通常用来兜底~~
3.异常的重新抛出
异常的重新抛出是指在 catch 块中捕捉到异常后,不是直接处理它,而是使用 throw 语句将其再次抛出。重新抛出的异常可以是原异常(即捕捉到的那个异常对象),也可以是一个新的异常对象。重新抛出异常通常用于在多层函数调用中传递异常信息,或者在 catch 块中执行了一些清理操作后需要让上层调用者处理异常。
如:
异常安全
①构造函数完成对象的构造和初始化,最好不要在构造函数中抛异常,否则可能导致对象不完整或没有完全初始化
②析构函数主要完成资源的清理,最好不要在析构函数内抛异常,否则可能导致资源泄漏
异常的优缺点
优点:
1.异常对象定义好了,相比错误码的方式,前者可以清晰准确的显示出错误的各种信息,甚至可以包含堆栈调用的信息,帮助更好的定位程序bug
2.传统错误码的返回方式最大问题就是:在函数调用链中,深层的函数返回错误,那么我们得层次返回错误,最外层才能拿到错误!
缺点:
执行流乱跳、混乱,分析程序时难以跟踪!