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

【C++篇】 异常处理

目录

1,异常的概念及使用

1.1,异常的概念

1.2,异常的抛出和捕获

1.3,栈展开

1.4,异常的重新抛出

1.5,异常安全问题

1.6,异常规范

2,标准库中的异常

小结:


1,异常的概念及使用

1.1,异常的概念

异常处理是C++用于管理 程序运行时错误的核心机制,通过try,catch,throw等关键字实现。它允许将错误检测与处理逻辑分离,提升代码的可读性和健壮性。

1.2,异常的抛出和捕获

  • 程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型和当前的调用链决定了应该由哪个catch来捕获处理该异常。

  • 当throw执行时,throw后面的代码不再执行。程序的执行从throw位置跳到与之匹配的catch模块。catch可能是同一个函数中的一个局部的catch,也可能是调用链中另一个函数中的catch。一旦程序开始异常处理,沿着调用链的对象都将被销毁。
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,拷贝对象再catch子句后销毁。

代码示例:

#include <iostream>
#include <stdexcept>

double divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零!");   //抛出异常对象
    }
    return a / b;
}

int main() {
    try {
        std::cout << divide(10, 0) << std::endl;
    } catch (const std::runtime_error& e) {   //捕获异常,处理异常
        std::cerr << "错误: " << e.what() << std::endl;
    }
    return 0;
}
// 输出:错误: 除数不能为零!

1.3,栈展开

  • 抛出 异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw是否再try块内部,如果在则查找匹配的catch语句,如果由匹配的,则跳到catch地方进行处理。
  • 如果当前函数没有try/catch子句,或者有但是类型不匹配,退出当前函数,继续在外层调用函数链中查找,上述查找catch的过程称为栈展开。

  • 如果到达main函数,依旧没有找到匹配的catch子句,则程序会调用terminate函数终止程序。有时候达到main函数,还没有找到匹配的,而我们是不希望终止程序的,所以main函数最后一般都使用catch(...),它可以捕获任意类型的异常,但不知道异常错误是什么
  • 如果找到匹配的catch子句处理后,catch子句代码会继续执行。

1.4,异常的重新抛出

有时catch到一个异常对象 后,需要对错误进行分类,其中的某种异常错误 需要进行特殊处理,其他异常则重新抛出给外层调用链处理。捕获异常后重新抛出,直接throw,就可以把捕获的对象重新抛出。

常见应用场景:

1,中间层处理部分逻辑

在多层调用中,中间层捕获异常后记录日志或释放资源,但不处理异常的根本原因:

void middleLayer() {
    try {
        someRiskyOperation();
    } catch (const std::exception& e) {
        logError(e.what()); // 记录日志
        throw; // 重新抛出,让上层处理
    }
}

2,包装异常

捕获原始异常后,抛出一个新的异常类型(如自定义异常),同时保留原始异常信息:

try {
    // ...
} catch (const DatabaseException& e) {
    throw MyAppException("Database error: " + std::string(e.what()));

3,跨线程异常传递

使用 std::exception_ptr 保存异常,稍后在另一线程中重新抛出:

std::exception_ptr eptr;
try {
    // 可能抛出异常的代码
} catch (...) {
    eptr = std::current_exception(); // 捕获并保存异常
}

// 在另一个线程中重新抛出
if (eptr) {
    std::rethrow_exception(eptr);
}

1.5,异常安全问题

  • 异常抛出后,后面的代码就不在执行了,前面申请了资源(内存),后面进行释放。但是中间可能会抛异常,导致资源没有释放,这里就由于异常引发了资源泄露,产生安全性的问题。这里需要使用智能指针的RAII方式解决。
  • 还有析构函数中,如果抛出异常也要谨慎处理,比如要释放 10个资源,释放到第5个时抛出异常,导致后面的资源没有释放,导致资源泄露了。

1.6,异常规范

C++11中 ,函数的参数列表后加一个noexcept表示该函数不会抛异常。但如果一个声明了noexcept的函数抛出了异常,程序会调用terminate终止程序。

noexcept还可以作为运算符去检测一个表达式是否会抛异常。

  • noexcept(expression):根据式表达的结果来决定是否可能抛出异常。如果表达式为 true,则表示不会抛出异常;如果为 false,则表示可能抛出异常。

2,标准库中的异常

C++标准库定义了一套自己的异常体系,基类是exception,所以我们在捕获异常时,捕获exception即可,要获取异常信息,调用what()函数,what()是一个虚函数,派生类可以重写。 

(1)C++标准库中常见的类型(继承自:std::exception)

  • std::logic_error:程序逻辑错误(如无效参数)。

  • std::runtime_error:运行时错误(如文件未找到)。

  • std::bad_alloc:内存分配失败(new失败时抛出)。

(2)自定义异常

通过继承std::exception创建自定义异常类:

class MyException : public std::exception {
public:
    MyException(const char* msg) : message(msg) {}
    const char* what() const noexcept override {
        return message.c_str();
    }
private:
    std::string message;
};

// 使用
throw MyException("自定义异常!");

小结:

(1)异常处理的性能影响

抛出异常时:栈展开(Stack Unwinding)和类型匹配会带来一定开销,不适合高频场景。

(2) 最佳实践

1,优先使用RAII(如智能指针):确保资源的自动释放

2,避免在析构函数中抛出异常:可能会导致程序终止(若异常未被捕获)

3,明确异常规格:使用noexcept标记不会抛异常的函数。

4,捕获异常按引用:避免对象切片如:catch (const std::exception& e)


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

相关文章:

  • vscode无法ssh连接远程机器解决方案
  • 解决基于FastAPI Swagger UI的文档打不开的问题
  • 第 26 场 蓝桥入门赛
  • 浅谈 HashMap 的扩容过程和 put 过程
  • JDBC笔记
  • ASP.NET Core中Filter与Middleware的区别
  • Docker安装+镜像+错误解决+win11【小记】
  • k8s部署elasticsearch
  • 深入理解C#结构型设计模式:类适配器与对象适配器
  • C++字符串相关内容
  • 编译原理面试问答
  • [权限提升] Linux 提权 维持 — 系统错误配置提权 - 通配符(ws)注入提权
  • 面试真题 | Momenta c++
  • 【大模型】硅基流动对接DeepSeek使用详解
  • 安当SLA操作系统登录双因素认证:全方位保障Windows系统登录安全
  • 【批量获取图片信息】批量获取图片尺寸、海拔、分辨率、GPS经纬度、面积、位深度、等图片属性里的详细信息,提取出来后导出表格,基于WPF的详细解决方案
  • 如何在Windows中配置MySQL?
  • 2. Mellanox 网卡的参数调优-LINK_TYPE_P1(GPU-AI-大模型,底层调优-测试)
  • 模型压缩 --学习记录2
  • 疯狂前端面试题(二)
  • 算法与数据结构(搜索旋转排序数组)
  • LLM应用实践(1)- 物流状态判断
  • c/c++蓝桥杯经典编程题100道(13)杨辉三角
  • Maven 中常用的 scope 类型及其解析
  • ubuntu24.04安装布置ros
  • 在亚马逊云科技上云原生部署DeepSeek-R1模型(上)