跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 异常处理机制详解

C++ 异常处理机制利用 throw、try、catch 关键字实现错误传递。区别于 C 语言错误码,异常支持类型匹配与栈展开,便于集中处理。但需警惕资源泄漏及 noexcept 规范下的程序终止风险。合理使用可提升代码健壮性。

安卓系统发布于 2025/10/22更新于 2026/8/1737 浏览
C++ 异常处理机制详解

异常是什么

概念上,异常是一种处理错误的方式。当一个函数遇到自己无法处理的错误时,会抛出异常,交由调用者处理。

在 C 语言中,我们主要通过错误码来处理错误。错误码本质是对错误信息进行编号,拿到码后还得去查表获取具体信息,比较麻烦。而 C++ 的异常机制则更直接,通过对象传递错误信息。

使用异常主要涉及三个关键字:

throw: 当函数出现问题时抛出异常 try: 包含可能出错的代码块,通常跟随 catch 模块 catch: 用来捕获并处理异常

栈展开

抛出异常后,程序会暂停当前函数的执行,开始寻找匹配的 catch 子句。首先检查 throw 是否在 try 块内部,如果在则查找匹配的 catch 语句;如果有匹配,则跳转到 catch 处处理。

如果当前函数中没有 try/catch 子句,或者类型不匹配,则退出当前函数,继续在外层调用链中查找。这种逐层向上查找的过程叫做栈展开(Stack Unwinding)。

如果一直找到 main 函数仍未找到匹配的 catch 子句,程序会调用标准库的 terminate 函数终止程序;如果找到了,处理完 catch 代码后,程序会继续执行后续逻辑。

异常的抛出和捕获

当程序出现问题时,我们会抛出一个对象来引发异常。该对象的类型以及当前的调用链决定了由哪个 catch 处理代码来接管。

被选中的处理代码是调用链中与该对象类型匹配且离抛出位置最近的那一个。根据抛出对象的类型和内容,异常处理部分能获知具体的错误情况。

异常匹配示意图

类型匹配规则

  • 类型不匹配: 如果类型不匹配,会继续向上传递。 类型不匹配示例
  • 执行流跳转: 当 throw 执行时,其后的语句将不再被执行。程序直接从 throw 位置跳到匹配的 catch 模块。
  • 通配符捕获: 使用 catch(...) 可以捕获任意类型的异常,但缺点是不知道具体是什么错误。
  • 拷贝机制: 抛出异常对象后,会生成一个异常对象的拷贝。因为抛出的可能是临时对象,所以必须生成拷贝,这个拷贝对象会在 catch 子句结束后销毁。

异常的重新抛出

有时我们需要对捕获到的异常进行分类处理。比如某种特定错误需要特殊处理,而其他错误则应重新抛出给外层调用链。

下面模拟一个聊天发消息的场景:网络不好时需要多次重试,其他错误则直接上报。

class Exception {
public:
    Exception(const string& errmsg, int id) : _errmsg(errmsg), _id(id) {}
    virtual string what() const { return _errmsg; }
    int getid() const { return _id; }
protected:
    string _errmsg;
    int _id;
};

class HttpException : public Exception {
public:
    HttpException(const string& errmsg, int id, const string type)
        : Exception(errmsg, id), _type(type) {}
    virtual string what() const {
        string str = "HttpException: ";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }
private:
    const string _type;
};

void _SeedMsg(const string& s) {
    if (rand() % 2 == 0) {
        throw HttpException("网络不稳定,发送失败", 102, "put");
    } else if (rand() % 7 == 0) {
        throw HttpException("你不是好友,发送失败", 103, "put");
    } else {
        cout << "发送成功" << endl;
    }
}

void SendMsg(const string& s) {
    for (size_t i = 0; i < 4; i++) {
        try {
            _SeedMsg(s);
            break;
        } catch (const Exception& e) {
            // 102 号错误是网络问题,尝试重试
            if (e.getid() == 102) {
                if (i == 3) throw; // 重试三次失败,重新抛出
                cout << "开始第" << i + 1 << "重试" << endl;
            } else {
                throw; // 其他错误直接重新抛出
            }
        }
    }
}

int main() {
    srand(time(0));
    string str;
    while (cin >> str) {
        try {
            SendMsg(str);
        } catch (const Exception& e) {
            cout << e.what() << endl << endl;
        } catch (...) {
            cout << "Unkown Exception" << endl;
        }
    }
    return 0;
}

异常的匹配原则

  1. 就近原则: 一般情况下抛出对象和 catch 类型完全匹配。如果有多个类型匹配,选择离抛出位置更近的那个。
  2. 继承关系: 实际中,抛出的类型和捕获的类型不一定完全相同。可以抛出派生类对象用基类捕获,这在多态场景下非常常用。
  3. 兜底处理: 如果到 main 函数异常仍未被匹配,程序会终止。为了避免非严重错误导致程序崩溃,一般 main 函数最后都会使用 catch(...) 作为兜底。

异常的安全问题

异常抛出后,后续代码不再执行。如果我们前面申请了资源(如内存、锁),后面才释放,中间抛出异常会导致资源泄漏。

因此,在中间也需要捕获异常,释放资源后再重新抛出。当然,后续学习智能指针章节时,会发现 RAII 方式是解决这类问题的更好手段。

同时要注意析构函数。例如析构函数要释放 5 个资源,若释放到第 2 个时抛出异常,后面的资源就没法释放,同样会造成泄漏。

double Divide(int a, int b) {
    if (b == 0) throw "Division by zero condition!";
    return (double)a / (double)b;
}

void Func() {
    int* array = new int[10];
    try {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    } catch (...) {
        cout << "delete []" << array << endl;
        delete[] array;
        throw; // 重新抛出,保留原始异常信息
    }
    cout << "delete []" << array << endl;
    delete[] array;
}

异常规范

在 C++98 中,函数参数列表后面接 throw() 表示不抛异常,接 throw(类型 1, 类型 2...) 表示可能抛出多种类型。

由于 C++98 方式过于复杂,实践中并不好用,C++11 进行了简化:

  • noexcept: 在函数参数列表后面加 noexcept 表示不会抛出异常。
  • 默认行为: 啥都不加表示可能会抛出异常。

注意:编译器并不会在编译时严格检查 noexcept。如果一个函数声明了 noexcept 但包含了 throw 语句或调用了可能抛异常的函数,编译器仍可能通过编译(有些会报警告)。但是,如果一个声明了 noexcept 的函数抛出了异常,程序会调用 terminate 终止程序。

noexcept 示例

此外,noexcept(expression) 还可以作为一个运算符检测表达式是否会抛出异常,可能会则返回 false,不会就返回 true。

noexcept 运算符

总结

优点: 可以清晰准确地展示错误的各种信息,便于集中处理。 缺点: 会导致程序执行流乱跳,分析困难,且容易导致内存泄漏问题。

合理使用异常机制,配合 RAII 和资源管理,能让 C++ 程序更加健壮。

目录

  1. 异常是什么
  2. 栈展开
  3. 异常的抛出和捕获
  4. 类型匹配规则
  5. 异常的重新抛出
  6. 异常的匹配原则
  7. 异常的安全问题
  8. 异常规范
  9. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • LangChain4j 实战:在 Java 项目中接入大模型的完整记录
  • 本地电脑训练专属大模型实战教程
  • VSCode Copilot 接入智谱 GLM-4 及任意大模型方案
  • Python 学习中的 27 个常见问题与解答
  • Kubernetes ResourceList 资源量加减运算工具类
  • 利用 AIGC 与 Photoshop 快速生成 Spine 2D 骨骼动画拆件 PSD
  • AR 技术在电力配电中的应用:带电作业解决方案
  • Claude Code Router 与 cpolar 组合优化 AI 开发流程
  • Spring Cloud Alibaba Nacos 注册中心与配置中心使用指南
  • Python 爬虫接单收费标准与薪资水平分析
  • OpenClaw 部署指南:接入 Minimax/DeepSeek 与飞书机器人
  • C++ 实现 JSON 与 HTTP 协议,从零构建 Web 计算器服务器
  • 通义万相 2.1 图生视频模型部署指南
  • 人工智能:大模型分布式训练与高效调参技术实战
  • Redis Set 数据类型 C++ 实战指南
  • 前端 SSG 详解:静态站点生成最佳实践
  • Vue 3 编程的 10 个实用技巧
  • Python vs Scala:大数据预处理工具链深度评测
  • 华为 VXLAN 静态配置基础与实战
  • PyAutoGUI:Python 桌面自动化框架详解

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online