C++异常处理完整深度解析

 更新时间:2026年07月18日 10:51:50   作者:Brilliantwxx  
在C++中,异常处理是一种用于处理运行时错误和异常情况的机制,它允许程序在遇到错误时优雅地终止或恢复到一种稳定状态,而不是直接崩溃,本文给大家介绍C++异常处理完整深度解析,感兴趣的朋友跟随小编一起看看吧

一、异常基础语法:try /throw/catch

1. 完整语法结构

try {
    // 正常业务代码,可能抛出异常
    throw 异常实例;
}
catch (异常类型1 参数) {
    // 处理类型1的错误
}
catch (异常类型2 &参数) {
    // 推荐引用捕获,避免对象拷贝切片
}
catch (...) {
    // 万能捕获,捕获所有未匹配异常,兜底处理
}
  • try:划定「受监控代码块」,块内抛出的异常才会被后续 catch 捕获;
  • throw:主动抛出异常对象,中断当前代码执行,触发栈展开;
  • catch:匹配对应类型的异常,处理错误逻辑;无匹配 catch 程序直接调用 std::terminate 崩溃。

2. throw 可以抛出任意类型

C++ 对抛出对象无类型限制,基础类型、字符串、自定义类、标准异常类均可:

// 1. 抛出内置类型
throw -1;
throw false;
// 2. 抛出std::string
throw std::string("数组下标越界");
// 3. 工程推荐:抛出标准异常对象
throw std::runtime_error("除数不能为0");

3. catch 匹配核心规则(高频考点)

严格类型匹配,不做隐式转换抛出 int 无法被 double、long 的 catch 捕获;抛出子类不能先写父类 catch,否则子类永远无法匹配。错误示范:

try { throw std::out_of_range("越界"); }
// 父类在前,子类直接失效
catch(std::exception& e) {}
catch(std::out_of_range& e) {}

正确顺序:子类异常在前,基类异常在后。

捕获优先使用引用 &直接值捕获会拷贝异常对象,带来性能损耗;基类值捕获会发生对象切片,丢失子类独有的错误信息。标准写法:catch(std::exception &e)

catch(...) 万能捕获只能放在最后仅用于兜底、打印日志、统一释放资源,无法获取异常具体信息。

二、栈展开 Stack Unwinding:异常核心底层机制

1. 什么是栈展开?

当执行 throw 抛出异常时,编译器会从抛出点反向遍历函数调用栈:

  1. 销毁当前 try 块内所有局部栈对象,自动调用析构函数;
  2. 逐层退出上层函数,销毁每层局部变量;
  3. 直到找到能匹配当前异常类型的 catch 块,停止栈展开,进入异常处理逻辑;
  4. 若遍历完整个调用栈仍无匹配 catch,调用 std::terminate() 终止程序。

2. 栈展开最大价值:自动释放资源,配合 RAII 实现异常安全

        C 语言出错时必须手动 free、fclose,一旦提前 return 或出错,极易遗漏释放;  C++ 局部对象出作用域自动析构,栈展开过程会强制执行析构,裸指针会泄漏,智能指针不会。

void badFunc() {
    int* p = new int[100];
    throw std::runtime_error("出错");
    // 栈展开只销毁局部变量p(指针本身),不会释放堆内存,内存泄漏!
}
void goodFunc() {
    std::unique_ptr<int[]> p(new int[100]);
    throw std::runtime_error("出错");
    // unique_ptr析构时自动delete堆内存,无泄漏
}

结论:工程中禁止裸指针管理堆资源,必须使用 RAII 容器 / 智能指针,保证异常安全。

三、C++ 标准异常体系 <stdexcept>

所有标准异常均继承自顶层基类 std::exception,定义头文件 <stdexcept>,基类提供虚函数 virtual const char* what() const noexcept,返回字符串形式错误描述。

1. 两大异常分支

(1)逻辑异常 logic_error:代码逻辑错误,开发阶段即可规避

属于程序编写失误,正常测试场景应提前拦截,理论上不应该在线上抛出

  • invalid_argument:函数传入非法参数
  • out_of_range:容器下标、数组越界访问
  • domain_error:数学函数定义域非法(如负数开平方根)
  • length_error:容器容量超出最大限制

(2)运行时异常 runtime_error:运行环境不可预测故障

外部环境导致,无法单纯靠代码规避,线上高频出现

  • range_error:数值计算结果超出值域范围
  • overflow_error:算术运算上溢
  • underflow_error:算术运算下溢

2. 标准异常完整使用示例

#include <iostream>
#include <stdexcept>
double divide(int a, int b) {
    if (b == 0) {
        // 运行时错误:除数为0
        throw std::runtime_error("Divide by zero error");
    }
    return static_cast<double>(a) / b;
}
int main() {
    try {
        divide(10, 0);
    } catch (std::runtime_error &err) {
        // what() 获取错误信息
        std::cout << "捕获异常:" << err.what() << std::endl;
    } catch (std::exception &err) {
        // 兜底捕获所有标准异常
        std::cout << "通用异常:" << err.what() << std::endl;
    }
    return 0;
}

四、自定义异常类(项目开发必备)

        标准异常仅提供通用错误描述,大型项目需要区分模块错误(数据库异常、网络异常、文件异常),通过继承 std::exception 实现自定义异常。

完整自定义异常模板

#include <exception>
#include <string>
// 数据库模块专属异常
class DBException : public std::exception {
private:
    std::string err_msg;
public:
    // 构造函数接收自定义错误信息
    explicit DBException(const std::string& msg) : err_msg(msg) {}
    // 重写虚函数what,加noexcept保证不抛异常
    const char* what() const noexcept override {
        return err_msg.c_str();
    }
};
// 使用
void sqlQuery() {
    bool connectFail = true;
    if (connectFail) {
        throw DBException("数据库连接超时,端口无法访问");
    }
}

优势

  1. 区分业务错误类型,catch 时精准定位故障模块;
  2. 可扩展成员:错误码、错误堆栈、请求 ID,方便日志排查;
  3. 完全兼容标准异常捕获逻辑。

五、异常修饰符:noexcept

1. 过时语法:动态异常说明(C++17 标准删除)

早期 C++ 使用 throw(类型列表) 声明函数允许抛出的异常:

// 仅能抛出int、string
void oldFunc() throw(int, std::string);
// 承诺不抛出任何异常
void noThrowFunc() throw();

缺陷:编译器不做强制检查,运行时抛出未声明异常直接崩溃,可读性差,新标准彻底废弃。

2. C++11 noexcept 关键字(工程唯一推荐)

两种用法

  1. noexcept:函数承诺不会抛出任何异常
void safeFunc() noexcept;
  1. noexcept(布尔表达式):条件式无异常,表达式为 true 则等价 noexcept
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b)));

关键规则

  1. 标记 noexcept 的函数内部若抛出异常,程序直接调用 std::terminate,不会进入任何 catch;
  2. C++ 隐式规定:析构函数默认 noexcept;如果析构抛出异常,栈展开过程会直接终止程序,属于严重 bug;
  3. 优化价值:编译器可对 noexcept 函数做性能优化,不需要生成异常跳转表。

工程强制规范

  • 资源释放函数、析构、内存回收函数一律加 noexcept;
  • 纯计算、无 IO、无错误场景函数标记 noexcept。

六、异常进阶:重新抛出异常 throw;

        捕获异常后,有时需要先打印日志、释放临时资源,再将异常向上层业务传递,分为两种重抛方式:

  1. throw; 无参数:原样重抛原有异常,保留完整子类信息(推荐)
  2. throw 新对象; 生成全新异常,丢失原有异常上下文
try {
    throw std::out_of_range("数组下标100越界");
} catch (std::exception &e) {
    std::cout << "记录错误日志:" << e.what() << std::endl;
    throw; // 重抛原始out_of_range异常,上层可精准捕获
}

七、三大异常安全保证(面试核心考点)

编写异常安全代码分为三个等级,项目开发最低要求满足基础保证,核心业务建议强保证。

  • 无抛出保证(No-throw guarantee)函数标记 noexcept,运行全程绝对不会抛出异常;如析构、内存释放、swap 交换函数。
  • 基础保证(Basic guarantee)抛出异常后:程序不崩溃、无内存 / 句柄泄漏、对象处于合法可用状态,但中间修改可能保留。大部分常规函数最低标准。
  • 强保证(Strong guarantee)事务型语义:要么函数完整执行成功,所有修改生效;抛出异常时,所有状态回滚到函数调用前,无任何副作用。实现方案:先在临时变量完成所有修改,全部逻辑无异常后,再交换到原对象。

八、工程最佳实践 & 高频踩坑总结

最佳实践

  • 异常仅处理罕见错误,不用代替普通判断异常底层存在性能开销,不要用异常处理常规业务分支(如用户输入为空、查询结果为空),这类场景用 if 判断;异常用于不可预期故障:断网、数据库崩溃、内存耗尽。
  • 永远不要在析构函数中抛出异常栈展开过程中若析构抛异常,会同时存在两个未处理异常,程序直接终止。析构内出错仅打印日志,禁止 throw。
  • 抛出对象,禁止抛出栈对象地址错误:throw &localErr;,栈展开后局部对象销毁,外部捕获到野指针;直接抛出值对象。
  • 分层捕获异常,底层抛标准 / 自定义异常,顶层统一兜底底层函数精准抛出细分异常,业务层 catch 对应类型处理,main 函数加catch(...)防止程序直接闪退。
  • RAII 优先,所有资源使用智能指针、容器管理杜绝裸 new/delete,避免栈展开内存泄漏。

常见致命坑

  1. catch 值捕获导致对象切片,丢失子类错误信息;
  2. catch 顺序颠倒,父类异常写在子类前面;
  3. noexcept 函数内部抛出异常,程序无提示直接崩溃;
  4. 函数仅靠返回值报错,混合异常,代码逻辑混乱;
  5. 抛出局部变量指针,产生野指针;
  6. 循环、高频接口频繁抛出异常,带来巨大性能损耗。

到此这篇关于C++异常处理完整深度解析的文章就介绍到这了,更多相关C++异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++实现动态线性表

    C++实现动态线性表

    这篇文章主要为大家详细介绍了C++实现动态线性表,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • 通过c语言调用系统curl动态库的示例详解

    通过c语言调用系统curl动态库的示例详解

    这篇文章中我们将通过一个简单的示例来讲解如何在Ubuntu系统中通过C语言调用动态库(共享库)的方法,我们将使用libcurl库,这是一个基于客户端的URL传输库,广泛用于各种程序和应用中以访问网页和服务器数据,需要的朋友可以参考下
    2024-03-03
  • C++ 创建桌面快捷方式 开始菜单的实现代码

    C++ 创建桌面快捷方式 开始菜单的实现代码

    这篇文章介绍了C++ 创建桌面快捷方式,开始菜单的实现代码,需要的朋友可以参考一下
    2013-06-06
  • C语言数据结构单链表接口函数全面讲解教程

    C语言数据结构单链表接口函数全面讲解教程

    这篇文章主要为大家介绍了C语言数据结构单链表所有接口函数的全面讲解教程,有需要朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2021-10-10
  • C++中的sort与自定义排序详解

    C++中的sort与自定义排序详解

    std::sort是C++模板函数,采用内省排序(混合快速、堆、插入排序)动态优化性能,确保O(nlogn)效率与鲁棒性,通过比较器、Lambda或重载运算符实现自定义排序逻辑
    2025-08-08
  • C语言实现大整数加减运算详解

    C语言实现大整数加减运算详解

    大数运算,顾名思义,就是很大的数值的数进行一系列的运算。本文通过实例演示如何进行C语言中的大整数加减运算,有需要的可以参考借鉴。
    2016-08-08
  • C++头文件解决问题场景小结

    C++头文件解决问题场景小结

    本文主要介绍了C++头文件解决问题场景小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03
  • 基于ios中的流状态的定义分析

    基于ios中的流状态的定义分析

    本篇文章介绍了,基于ios中的流状态的定义分析。需要的朋友参考下
    2013-05-05
  • 利用Matlab制作抖音同款含褶皱面料图

    利用Matlab制作抖音同款含褶皱面料图

    这篇文章主要介绍了如何利用Matlab制作抖音的同款含褶皱面料图,文中的示例代码讲解详细,对我们学习Matlab有一定帮助,需要的可以参考一下
    2022-03-03
  • C++11新特性之自定义字面量

    C++11新特性之自定义字面量

    这篇文章主要介绍了C++11新特性之自定义字面量的相关资料,帮助大家更好的学习c++,感兴趣的朋友可以了解下
    2020-08-08

最新评论