C++ 异常处理机制与自定义异常体系
目录
1.C语言传统的处理错误的方式 😊
1. 终止程序
2. 返回错误码
3.实际使用中的情况
2. C++异常概念🌼
2.1 C++异常的基本概念
2.2异常的抛出和匹配原则
2.3 异常的重新抛出
2.4 异常安全
2.5 异常规范
3. 自定义异常体系 💕😘
3.1 自定义异常类
3.2 自定义多个异常类型
3.3. 捕获自定义异常
3.4 异常层次结构设计
4.C++标准库的异常体系 😁😁
5.异常的优缺点🤣🤣
前言😘😘😘
在程序开发中,错误和异常的处理是至关重要的,它直接影响到程序的健壮性和稳定性。C语言的错误处理主要依赖返回值和错误码,虽然这种方式简单直接,但在复杂的程序中,错误处理代码往往难以维护且容易出错。相比之下,C++引入的异常处理机制提供了一种更为高效和灵活的错误处理方式,使得程序的错误管理更加清晰和优雅。
本节将详细介绍C++异常处理的相关概念、用法以及如何通过自定义异常体系来满足程序的需求。同时,我们将对比C语言的传统错误处理方式,分析C++异常机制的优缺点,并探讨标准库中提供的异常体系,帮助开发者更好地理解和使用C++的异常处理功能。
1.C语言传统的处理错误的方式 😊
C语言传统的错误处理机制主要有两种方式:终止程序和返回错误码。这两种方式虽然简单易用,但各自也有其局限性和缺陷,尤其是在处理复杂错误或大规模程序时,往往会导致维护上的困难。
1. 终止程序
一种常见的错误处理方式是直接终止程序,这种方式通常是通过使用assert宏实现的。assert会在程序运行时对条件进行检查,如果条件不满足,则程序会立即终止并输出错误信息。
代码如下:
#include void foo(int x) { assert(x != 0); // 如果x为0,程序终止 printf("x is not zero\n"); } int main() { foo(0); // 触发断言,程序终止 return 0; }
缺陷:
- 用户难以接受:程序直接终止,尤其是当发生一些小错误或边界条件时,用户体验很差。
- 适用场景有限:这种方式适用于严重错误(如内存错误、除0错误等),但不适合所有情况,因为大多数程序错误并不需要完全终止程序。
当程序遇到无法恢复的错误时,assert可以有效地帮助开发者检测出问题。但是,当程序出现一些非致命错误时,用户希望程序能够优雅地处理,而不是直接崩溃。
2. 返回错误码
另一个常见的错误处理方式是通过返回错误码来通知程序出现了问题。这种方法在C语言中非常普遍,许多标准库函数(如malloc、fopen等)都通过返回一个特殊的错误码来表示函数执行失败。开发者需要根据返回值来判断错误,并做相应的处理。
比如,malloc在分配内存失败时返回NULL,fopen在打开文件失败时返回NULL,errno则是一个全局变量,用于记录最近一次系统调用的错误码。
缺陷:
- 需要手动检查错误码:程序员必须检查每个函数调用的返回值,以便发现错误。这导致了大量重复的错误处理代码,增加了维护成本。
- 错误信息不够直观:虽然可以通过errno来获取详细的错误信息,但这通常不如异常机制直观。错误码本身通常是数字,缺乏对错误本质的描述,需要额外的逻辑去理解错误码的含义。
- 错误处理分散:程序中多处调用的函数可能会返回不同的错误码,处理这些错误的逻辑往往分散在代码的各个地方,导致代码的可读性差。
3.实际使用中的情况
在实际的C语言开发中,返回错误码是最常见的错误处理方式。C语言没有内建的异常机制,所以程序员必须通过检查每个函数调用的返回值来手动处理错误。对于一些简单的错误,返回错误码通常足够。但对于较复杂的应用程序,错误码的使用可能变得冗长且难以维护。
在一些非常严重的错误情况下(如内存分配失败、文件操作失败等),开发者有时会选择直接终止程序。例如,在发现内存分配失败时,程序可能无法继续执行,这时直接通过exit()或其他方式终止程序可以避免进一步的错误。
2. C++异常概念🌼
C++的异常机制是一种专门用于处理错误和特殊情况的机制,可以在程序运行时中断当前的控制流,并跳转到一个可以处理该错误的代码块。这种机制使得程序能够优雅地应对各种错误,而不是像C语言那样依赖返回错误码或直接终止程序。
2.1 C++异常的基本概念
-
异常 (Exception)
- 异常是指程序在运行时遇到的一种错误或意外情况,这种情况可能会导致程序无法正常继续执行。
- 异常通过throw关键字抛出,表示错误已经发生。
- 异常可以是任意的C++对象(如数字、字符串、自定义类对象),但通常建议使用标准库中的异常类(如std::exception及其派生类)。
-
异常处理机制 C++通过三种关键字实现异常处理:
- try:定义一个代码块,用于包含可能发生异常的代码。
- throw:在异常发生时,用于抛出异常。
- catch:捕获异常,并定义如何处理它。
2.2异常的抛出和匹配原则
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,
所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似
于函数的传值返回)
4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,
使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。
在函数调用链中异常栈展开匹配原则
1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则
调到catch的地方进行处理。
2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的
catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异
常,否则当有异常没捕获,程序就会直接终止。
4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
代码案例:
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
-