当前位置: 首页 > article >正文

C++中关于异常的简单分析

1.在 C++ 中,异常处理机制有着多方面的重要用途:
 

  1. 错误处理

    • 区分正常与异常流程:函数执行可能因为各种不可预见的原因失败,例如文件不存在、网络连接中断、内存分配失败等。使用异常能清晰地将错误处理代码和正常业务逻辑分开,让代码可读性更强。例如,std::vector在访问越界时会抛出std::out_of_range异常,开发者无需在每次访问元素时都用繁琐的 if 语句去检查索引合法性,正常访问逻辑不受干扰,出错时则交由专门的异常处理模块解决。
    • 传递错误信息:异常对象可以携带详细的错误信息,方便调试与定位问题。比如抛出std::runtime_error异常时,能在构造函数中传入描述错误的字符串,在catch块里通过what() 函数获取该字符串,告知开发者具体哪里出错了。
  2. 资源管理

    • 保证资源释放:在栈展开过程中,当异常发生时,位于异常抛出点和捕获点之间函数调用栈里的局部对象,其析构函数会被自动调用。这对于管理动态分配的资源(如内存、文件句柄、网络连接等)尤为关键。例如,在一个函数里打开了文件,中途发生异常,如果没有异常机制,文件可能无法关闭,而利用异常,即便异常抛出,文件对象的析构函数也会执行关闭文件的操作。
    • 简化资源获取逻辑:像智能指针这类资源管理类,内部也是依赖异常机制来确保内存安全。例如std::unique_ptr,如果在构造函数里分配内存失败,会抛出std::bad_alloc异常,外部使用时无需额外检查内存分配情况,代码更加简洁。
  3. 程序健壮性与容错

    • 提升系统稳定性:大型程序、长时间运行的服务往往难以预测所有可能出错的场景。异常处理提供了一种兜底的容错机制,使得程序在遭遇意外错误时,不会直接崩溃,而是有机会进行补救,例如记录错误日志、尝试重启某个模块,甚至是向运维人员发送报警信息,维持系统的基本运转。
    • 模块化与代码复用:不同模块开发时难以预估自身会在何种完整应用场景下被调用,通过异常抛出自身无法处理的错误,让调用模块去捕获和处理,提升了代码的模块化程度,利于代码复用。例如,一个底层的加密算法库遇到非法输入参数时抛出异常,上层的加密服务调用该库,捕获异常后决定是提示用户重新输入还是调整参数再次调用。

2.在 C++ 中,异常处理的基本顺序如下:

  1. 抛出异常

    • 当程序运行过程中出现错误或异常情况时,使用 throw 关键字抛出一个异常对象。这个对象可以是内置类型(如 intdouble 等),也可以是自定义的类类型。例如
     
    if (some_error_condition) {
        throw std::runtime_error("出错啦!");
    }
    
  2. 异常查找与匹配

    • 异常被抛出后,程序会暂停当前函数的执行,开始在调用栈中逆向查找匹配的 try - catch 块。调用栈是函数调用的层次结构,当前正在执行的函数位于栈顶,main 函数位于栈底。
    • 查找从抛出异常的位置开始,依次检查调用栈中的各个函数,看是否存在包含该异常类型的 catch 块。例如:
     

    try {
        // 可能抛出异常的代码
        someFunction();
    } catch (const std::runtime_error& e) {
        // 处理 std::runtime_error 类型的异常
        std::cerr << "捕获到运行时异常: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        // 处理 std::exception 及其派生类的异常
        std::cerr << "捕获到通用异常: " << e.what() << std::endl;
    } catch (...) {
        // 捕获所有其他未处理的异常类型
        std::cerr << "捕获到未知异常" << std::endl;
    }
    
     
    • 异常匹配时按照 catch 块声明的顺序进行,一旦找到匹配的 catch 块,就进入该块处理异常,后续的 catch 块不再检查。这里匹配规则要求异常对象类型与 catch 参数类型要么完全相同,要么异常对象类型是 catch 参数类型的派生类
  3. 异常处理

    • 进入匹配的 catch 块后,执行其中的代码,处理异常情况。在 catch 块内,可以进行错误记录、资源清理、状态恢复等操作。处理完毕后,程序会继续从 try - catch 块之后的代码执行,除非 catch 块内又抛出了新的异常。
    • 如果在调用栈中一直没有找到匹配的 catch 块,最终会调用标准库提供的 terminate 函数,导致程序异常终止。默认情况下,terminate 调用 abort,直接结束程序运行。不过,也可以通过 set_terminate 函数自定义 terminate 的行为。
  4. 栈展开

    • 在异常查找过程中,当从一个函数跳到另一个函数去匹配 catch 块时,介于抛出异常位置和捕获异常位置之间的函数调用会依次退栈,这个过程称为栈展开。栈展开时,自动变量会按其定义顺序的逆序销毁,确保资源得到合理释放。如果这些自动变量是类对象,会调用它们的析构函数。

3.在 C++ 中,可以抛出多种类型的异常,主要包括以下几类:

  1. 内置数据类型

    • intdoublechar等基本数据类型都能被抛出。例如,你可以编写:
     
    if (some_error_condition) {
        throw 42; 
    }
    
     

    不过,抛出内置类型往往无法携带足够丰富的错误信息,实用性稍差。

  2. 标准异常类

    • C++ 标准库定义了一系列异常类,它们都派生自std::exception 这个基类,位于<exception>头文件中。
    • std::exception:这是所有标准异常的基类,它提供了一个虚成员函数what(),用于返回错误描述的字符串。不过,std::exception本身通常不直接抛出,而是作为更具体异常类的抽象。
    • std::runtime_error:用于表示运行时错误,例如算法逻辑错误、非法状态等。构造函数接受一个C++字符串参数,用于描述具体错误,调用what()函数时能返回该字符串。示例
    try {
        if (a > b && b < 0) {
            throw std::runtime_error("非法的比较条件");
        }
    } catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
    }
    
     
    • std::logic_error:用来指明程序逻辑中的错误,比如非法参数。常见的派生类有std::invalid_argument(参数无效) 和std::out_of_range(访问越界)。
    • std::bad_alloc:当new操作符无法分配到足够内存时抛出该异常,例如:
     
    try {
        int* ptr = new int[10000000000];
    } catch (const std::bad_alloc& e) {
        std::cerr << e.what() << std::endl;
    }
    
  3. 自定义异常类

    • 程序员可以根据具体项目需求自定义异常类,只要让自定义类派生自std::exception或者它的某个派生类即可。这样能高度贴合项目中的特定错误场景,携带专属的错误信息。例如:
    class MyException : public std::runtime_error {
    public:
        MyException(const std::string& msg) : std::runtime_error(msg) {}
    };
    
    try {
        // 假设某个条件触发错误
        if (x == 0) {
            throw MyException("x不能为零");
        }
    } catch (const MyException& e) {
        std::cerr << e.what() << std::endl;
    }
    
  4. 指针类型:理论上,指针也可以作为异常抛出,但这并不提倡,因为指针抛出后,接收端很难判断它指向的内存是否有效、安全,容易引发悬空指针等内存相关的危险问题。

4.C++ 标准库提供了一系列异常类

C++ 标准库提供了一系列异常类,这些类大多派生自 std::exception 基类,位于 <exception> 头文件中。它们各自有着明确的用途,方便开发者处理不同类型的错误场景:

  1. std::exception

    • 概述:这是所有标准异常类的抽象基类,它提供了一个虚成员函数 what(),目的在于返回一个以空字符结尾的字符数组,用来描述错误。不过它本身通常不会直接被抛出,更多是作为一个公共接口,供派生类去实现更具体的错误描述。
    • 用途:在编写自定义异常类时,常常让其派生自 std::exception,以此复用 what() 机制,保证自定义异常类与标准库异常类体系兼容。
  2. std::logic_error

    • 概述:该类用于表示那些可以在程序编译时检测到的逻辑错误,它派生自 std::exception,位于 <stdexcept> 头文件。
    • 派生类及用途
      • std::invalid_argument:当函数接收到无效的参数值时抛出。例如,函数期望接收一个正数,但传入了负数,就可以抛出这个异常 ,sqrt(-1) 这种非法数学运算调用就适合抛出此异常。
      • std::domain_error:常用于数学函数中,当输入值不在函数的定义域内时抛出。像对负数求对数这类操作,就该抛出 std::domain_error
      • std::out_of_range:在访问容器、字符串等对象时,若索引、迭代器超出合法范围,就会抛出这个异常。例如,访问 std::vector 中不存在的元素索引时,容器会抛出 std('out_of_range')
  3. std::runtime_error

    • 概述:用于表示那些只能在程序运行期间才能发现的错误,同样派生自 std::exception,在 <stdexcept> 头文件中。
    • 派生类及用途
      • std::range_error:如果计算结果超出了预先设定的有效范围,会抛出该异常。比如在一个高精度数值计算算法中,结果值超过了预期的最大范围。
      • std::overflow_error:当算术运算产生溢出时抛出,像两个极大的 int 数相乘导致结果超出 int 类型能表示的范围。
      • std::underflow_error:主要针对浮点型运算,当结果因为太小而无法精确表示,就抛出这个异常,例如非常小的浮点数除法。
  4. std::bad_alloc

    • 概述:在使用 new 运算符分配内存失败时抛出,位于 <new> 头文件。
    • 用途:内存分配是动态内存管理中的关键环节,std::bad_alloc 让程序有机会在内存分配失败时做出合理应对,而不是悄无声息地崩溃,例如提示用户关闭一些后台程序释放内存,再尝试重新分配。
  5. std::bad_cast

    • 概述:用于 dynamic_cast 转换失败的场景,在 <typeinfo> 头文件。当试图把一个对象通过 dynamic_cast 转换到不兼容的类型时,就会抛出这个异常。
    • 用途:保证类型转换的安全性,让开发者能捕获并处理类型转换出错的情况,避免程序因为非法的类型转换而出现未定义行为。
  6. std::bad_typeid

    • 概述:如果在 typeid 操作符作用于一个空指针时,会抛出这个异常,位于 <typeinfo> 头文件。

    • 用途:防止因空指针引发的类型查询错误,使得程序在这类特殊情况下也能有序处理。


http://www.kler.cn/a/465730.html

相关文章:

  • 贵州省贵安新区地图+全域数据arcgis格式shp数据矢量路网地名+卫星影像底图下载后内容测评
  • C#设计模式(行为型模式):状态模式
  • 东京大学联合Adobe提出基于指令的图像编辑模型InstructMove,可通过观察视频中的动作来实现基于指令的图像编辑。
  • 4进货+后台事务
  • 【数据结构-堆】力扣2530. 执行 K 次操作后的最大分数
  • Flink CDC 自定义函数处理 SQLServer XML类型数据 映射 doris json字段方案
  • C# 设计模式概况
  • Python爬虫入门(1)
  • 【Patroni官方文档】介绍与目录
  • 【谷歌开发者月刊】十二月精彩资讯回顾,探索科技新可能
  • 【C++】穿越时光隧道,拾贝史海遗珍,轻启C++入门之钥,解锁程序之奥秘(首卷)
  • 随机种子定不死找bug
  • python 字符串算法
  • CTFshow—远程命令执行
  • 区块链方向学习路线
  • 音视频-----RTSP协议 音视频编解码
  • 国产数据库AntDB插件pg_profile安装说明
  • xdoj isbn号码
  • echarts 饼图超过9种颜色就重复了,如何自定义颜色
  • 【vue项目中漏洞修复】
  • Spring源码分析之事件机制——观察者模式(一)
  • 硬件-射频-PCB-常见天线分类-ESP32实例
  • 855. 考场就座
  • 线性代数自学资源推荐我的个人学习心得
  • Java 代码审计入门-07】SQL关键字
  • HTML5新特性|05 CSS3边框CSS3背景