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

C++ 异常处理机制:捕获、自定义与实战

C++ 异常处理通过 try-catch-throw 机制分离错误检测与处理逻辑。标准异常库提供基础类型,自定义异常类继承 std::exception 可增强语义。掌握匹配规则、noexcept 声明及 RAII 资源管理是保障程序健壮性的关键。实战中应避免滥用异常控制流,优先使用智能指针防止内存泄漏,确保异常安全。本文涵盖基础语法、自定义异常设计、高级特性及文件读写实战案例,帮助开发者构建容错能力强的 C++ 应用。

涅槃凤凰发布于 2026/3/28更新于 2026/7/2130 浏览
C++ 异常处理机制:捕获、自定义与实战

C++ 异常处理机制:捕获、自定义与实战

核心目标

掌握异常处理的核心概念(异常、抛出、捕获、处理)及基本语法,理解 try-catch-throw 语句的执行流程。学会自定义异常类以满足实际开发中的个性化场景需求,同时掌握异常处理的最佳实践,规避内存泄漏和异常安全问题。此外,还需理解 noexcept 关键字的使用场景,结合实战案例提升代码的健壮性和容错能力。

异常处理概述

什么是异常处理

异常处理是 C++ 中处理程序运行时错误的机制,核心在于将错误检测与错误处理分离。在程序出错的地方(如除以零、内存分配失败)抛出异常,在合适的地方捕获并处理,避免程序直接崩溃,从而提升代码健壮性。

生活中的类比有助于理解:

  • 快递配送:快递员发现地址错误时上报快递公司,由客服联系收件人解决。
  • 餐厅点餐:厨师发现食材耗尽时告知服务员,由服务员向顾客说明并推荐其他菜品。
为什么需要异常处理

在异常处理出现前,程序通常通过返回值判断是否出错,但存在明显缺陷:

// 传统错误处理:通过返回值判断(缺陷明显)
int divide(int a, int b) {
    if (b == 0) {
        return -1; // 用 -1 表示错误,但 -1 可能是合法计算结果
    }
    return a / b;
}

int main() {
    int result = divide(10, 0);
    if (result == -1) {
        cout << "除数不能为 0!" << endl;
    } else {
        cout << "结果:" << result << endl;
    }
    return 0;
}

传统错误处理的缺陷包括:返回值可能与合法结果冲突;需手动检查每个函数返回值,代码冗余且易遗漏;错误传播困难,多层函数调用时需逐层传递错误状态。

异常处理的优势则体现在:错误检测与处理分离,代码结构清晰;异常可跨函数、跨层级传播,无需逐层传递;可携带丰富的错误信息;避免程序因小错误直接崩溃,提升用户体验。

C++ 异常处理的核心组件

C++ 异常处理依赖三个核心关键字:

  1. throw:抛出异常(检测到错误时触发)。
  2. try:尝试执行可能抛出异常的代码块(异常检测范围)。
  3. catch:捕获并处理异常(匹配对应的异常类型,执行处理逻辑)。

核心流程为:try 块中执行代码 → 若发生错误,throw 抛出异常 → 程序跳转到最近的匹配 catch 块 → 执行 catch 中的处理逻辑 → 处理完成后,程序从 catch 块后继续执行。

异常处理基本语法与执行流程

基本语法格式
try {
    // 可能抛出异常的代码块
    // 可能出错的操作
    if (错误条件) {
        throw 异常值; // 抛出异常(异常值可是任意类型:int、string、自定义类等)
    }
} catch (异常类型 1 异常变量) {
    // 处理异常类型 1 的逻辑
} catch (异常类型 2 异常变量) {
    // 处理异常类型 2 的逻辑
} catch (...) {
    // 捕获所有未匹配的异常(兜底处理)
}

语法解析要点:

  • try 块必须紧跟一个或多个 catch 块,不能单独存在。
  • throw 表达式可抛出任意类型的值,抛出后立即终止当前函数执行,跳转到匹配的 catch 块。
  • catch 块按顺序匹配异常类型,catch (...) 是万能捕获,需放在所有 catch 块最后。
  • 异常变量可选,用于获取抛出的异常信息。
执行流程详解

以下示例演示了基本的异常处理流程(除数为 0 异常):

#include <iostream>
using namespace std;

int divide(int a, int b) {
    if (b == 0) {
        throw string("错误:除数不能为 0!");
    }
    return a / b;
}

int main() {
    int x = 10, y = 0;
    try {
        cout << "尝试执行除法运算..." << endl;
        int result = divide(x, y); // 可能抛出异常
        cout << x << " / " << y << " = " << result << endl; // 若抛出异常,此句不执行
    } catch (const string& err_msg) {
        cout << "捕获到异常:" << err_msg << endl; // 处理异常
    } catch (...) {
        cout << "捕获到未知异常!" << endl;
    }
    cout << "程序继续执行..." << endl; // 异常处理后,程序继续运行
    return 0;
}

运行结果为:

尝试执行除法运算...
捕获到异常:错误:除数不能为 0!
程序继续执行...

流程拆解:

  1. 程序进入 try 块,执行 divide(10, 0)。
  2. divide 函数检测到 b=0,throw 抛出 string 类型异常。
  3. 程序立即终止 try 块执行,跳转到 main 函数中最近的 catch 块。
  4. 第一个 catch 块匹配 string 类型异常,执行处理逻辑(打印错误信息)。
  5. catch 块执行完成后,程序从 catch 块后继续执行。
异常的匹配规则

catch 块按声明顺序匹配异常类型,规则如下:

  1. 精确匹配:异常类型与 catch 声明类型完全一致。
  2. 派生类匹配:抛出的派生类异常可被基类类型的 catch 块捕获。
  3. 类型转换匹配:仅支持有限的隐式转换。
  4. catch (...) 匹配所有未被前面 catch 块捕获的异常,必须放在最后。

注意:catch 块的声明顺序至关重要。若将基类异常的 catch 块放在派生类之前,会导致派生类异常被基类 catch 块捕获,派生类的 catch 块永远无法执行。

标准异常库

C++ 标准库提供了一系列预定义的异常类,均继承自 std::exception 基类,定义在 <exception> 头文件中。常用标准异常如下:

异常类描述适用场景
std::exception所有标准异常的基类兜底捕获标准异常
std::logic_error逻辑错误如无效参数、非法状态
std::invalid_argument无效参数错误如向函数传递非法参数
std::out_of_range超出范围错误如数组索引越界
std::runtime_error运行时错误如除以零、文件打开失败
std::overflow_error溢出错误如数值计算溢出
std::bad_alloc内存分配失败错误如 new 分配内存失败

使用标准异常类时,可以利用 what() 方法返回 C 风格字符串,包含异常的简要描述,可用于日志输出或用户提示。

自定义异常类

标准异常类虽能满足常见场景,但实际开发中,我们常需要自定义异常(如业务相关的'用户不存在异常''权限不足异常')。自定义异常类需遵循以下原则:继承自标准异常类(推荐 std::exception 或其派生类),重写 what() 方法,提供必要的构造函数,类名清晰体现异常类型。

自定义异常类的实现

以下示例实现了业务相关的自定义异常类:

#include <iostream>
#include <exception>
#include <string>
using namespace std;

// 1. 基础业务异常类(继承自 std::exception)
class BusinessException : public exception {
private:
    string err_msg;
public:
    BusinessException(const string& msg) : err_msg(msg) {}
    const char* what() const noexcept override {
        return err_msg.c_str();
    }
};

// 2. 派生异常类:用户不存在异常
class UserNotFoundException : public BusinessException {
public:
    UserNotFoundException(int user_id) 
        : BusinessException("用户不存在:ID=" + to_string(user_id)) {}
};

// 3. 派生异常类:权限不足异常
class PermissionDeniedException : public BusinessException {
public:
    PermissionDeniedException(const string& username, const string& operation)
        : BusinessException("权限不足:用户\"" + username + "\"无法执行\"" + operation + "\"操作") {}
};

// 模拟业务函数:根据用户 ID 查询用户
void query_user(int user_id) {
    if (user_id < 1000 || user_id > 9999) {
        throw UserNotFoundException(user_id);
    }
    cout << "查询成功:用户 ID=" << user_id << endl;
}

// 模拟业务函数:执行敏感操作
void execute_sensitive_operation(const string& username) {
    if (username != "admin") {
        throw PermissionDeniedException(username, "删除数据");
    }
    cout << "操作成功:用户\"" << username << "\"执行删除数据操作" << endl;
}

int main() {
    try {
        query_user(123);
    } catch (const UserNotFoundException& e) {
        cout << "业务异常:" << e.what() << endl;
    } catch (const BusinessException& e) {
        cout << "业务异常:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "系统异常:" << e.what() << endl;
    }
    cout << endl;

    try {
        execute_sensitive_operation("test");
    } catch (const PermissionDeniedException& e) {
        cout << "业务异常:" << e.what() << endl;
    } catch (const BusinessException& e) {
        cout << "业务异常:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "系统异常:" << e.what() << endl;
    }
    return 0;
}

运行结果为:

业务异常:用户不存在:ID=123
业务异常:权限不足:用户"test"无法执行"删除数据"操作

自定义异常的优势在于语义清晰、层次分明、信息丰富且兼容标准。注意事项包括:what() 方法必须重写为 const noexcept;异常类应尽量轻量;优先使用引用捕获异常以避免拷贝开销。

异常处理的高级特性

异常规格说明与 noexcept

C++11 前可通过 throw(类型列表) 声明函数可能抛出的异常类型,称为异常规格说明。但该语法已废弃,推荐使用 noexcept 关键字。

noexcept 用于声明函数是否可能抛出异常:

  • void func() noexcept; 声明函数不会抛出任何异常。
  • void func() noexcept(false); 声明函数可能抛出异常(等价于不写)。

noexcept 的核心作用包括编译器优化(省略异常处理相关代码)、明确接口契约以及影响标准库行为(如 vector 的 push_back 若元素移动构造为 noexcept 会使用移动语义)。注意:若 noexcept 函数实际抛出了异常,程序会调用 std::terminate() 终止,无法通过 try-catch 捕获。

异常的传播与重新抛出

异常的跨函数传播:异常抛出后,若当前函数没有匹配的 catch 块,异常会向上传播到调用该函数的上层函数,直到找到匹配的 catch 块;若传播到 main 函数仍未捕获,程序会调用 std::terminate() 终止。

异常的重新抛出:有时需要在 catch 块中处理部分逻辑后,将异常重新抛出给上层函数处理,使用 throw;(不带参数)实现。这重新抛出的是原始异常对象,不会创建新的异常对象。

异常安全

异常安全是指程序抛出异常时,确保不会发生内存泄漏、数据状态一致、资源被正确释放。常见的解决方案包括使用智能指针、资源获取即初始化(RAII)模式以及使用容器和标准库组件。

例如,使用 unique_ptr 管理内存,即使发生异常也不会泄漏。RAII 模式则将资源封装在类中,利用析构函数自动释放资源,如 FileGuard 类管理文件流,确保异常发生时文件自动关闭。

异常处理的常见错误与最佳实践

常见错误
  1. 过度使用异常:将异常用于正常的控制流(如判断函数返回结果),导致代码效率降低。
  2. 捕获所有异常却不处理:使用 catch (...) 但未做任何处理,导致问题排查困难。
  3. 抛出非异常类型的对象:抛出基本类型或未继承自 std::exception 的自定义类,导致异常处理不统一。
  4. 异常对象切片:按值捕获异常而非按引用捕获,导致派生类异常的特有信息丢失。
最佳实践
  1. 明确异常使用场景:仅在异常情况(如内存分配失败、IO 错误)使用异常,正常控制流使用返回值。
  2. 优先使用标准异常或自定义异常类:系统级错误使用标准异常,业务级错误使用继承自 std::exception 的自定义异常。
  3. 按引用捕获异常:使用 catch (const Exception& e),避免拷贝开销和对象切片。
  4. 合理组织 catch 块顺序:派生类在前,基类在后,catch (...) 作为兜底。
  5. 保证异常安全:使用智能指针和 RAII 模式管理资源。
  6. 记录异常信息:捕获异常后记录详细信息便于排查。
  7. 避免在析构函数中抛出异常:可能导致程序终止。

实战案例:文件读写的异常处理

问题描述

实现一个文件读写工具类,支持读取和写入文件内容,要求处理文件操作中的常见异常,使用自定义异常类,保证异常安全,并提供友好的用户提示。

实现思路
  1. 自定义文件相关异常类(继承自 std::exception)。
  2. 基于 RAII 模式实现文件工具类 FileHandler,管理文件流资源。
  3. 实现 read_file 和 write_file 方法,抛出对应的自定义异常。
  4. 在主函数中捕获异常,记录日志并提示用户。
代码实现
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <exception>
using namespace std;

// 1. 自定义文件异常基类
class FileException : public exception {
protected:
    string err_msg;
public:
    FileException(const string& filename, const string& reason) {
        err_msg = "文件操作异常:文件\"" + filename + "\", 原因:" + reason;
    }
    const char* what() const noexcept override {
        return err_msg.c_str();
    }
};

// 2. 派生异常:文件打开失败
class FileOpenException : public FileException {
public:
    FileOpenException(const string& filename, const string& reason)
        : FileException(filename, "打开失败 - " + reason) {}
};

// 3. 派生异常:文件读取失败
class FileReadException : public FileException {
public:
    FileReadException(const string& filename, const string& reason)
        : FileException(filename, "读取失败 - " + reason) {}
};

// 4. 派生异常:文件写入失败
class FileWriteException : public FileException {
public:
    FileWriteException(const string& filename, const string& reason)
        : FileException(filename, "写入失败 - " + reason) {}
};

// 5. 文件工具类(RAII 模式)
class FileHandler {
private:
    string filename;
    fstream file_stream;
public:
    FileHandler(const string& filename, ios_base::openmode mode) : filename(filename) {
        file_stream.open(filename, mode);
        if (!file_stream.is_open()) {
            throw FileOpenException(filename, "无法打开文件(可能不存在或权限不足)");
        }
        cout << "日志:文件\"" << filename << "\"打开成功" << endl;
    }

    ~FileHandler() {
        if (file_stream.is_open()) {
            file_stream.close();
            cout << "日志:文件\"" << filename << "\"关闭成功" << endl;
        }
    }

    vector<string> read_file() {
        vector<string> content;
        string line;
        if (!file_stream.good()) {
            throw FileReadException(filename, "文件流状态异常");
        }
        while (getline(file_stream, line)) {
            content.push_back(line);
        }
        if (file_stream.bad()) {
            throw FileReadException(filename, "读取过程中发生 IO 错误");
        }
        cout << "日志:文件\"" << filename << "\"读取完成,共" << content.size() << "行" << endl;
        return content;
    }

    void write_file(const vector<string>& content) {
        if (!file_stream.good()) {
            throw FileWriteException(filename, "文件流状态异常");
        }
        for (const string& line : content) {
            file_stream << line << endl;
            if (file_stream.fail()) {
                throw FileWriteException(filename, "写入数据失败(可能磁盘已满)");
            }
        }
        file_stream.flush();
        if (file_stream.fail()) {
            throw FileWriteException(filename, "刷新缓冲区失败");
        }
        cout << "日志:文件\"" << filename << "\"写入完成,共" << content.size() << "行" << endl;
    }
};

// 辅助函数:打印文件内容
void print_file_content(const vector<string>& content) {
    cout << "\n文件内容:" << endl;
    for (int i = 0; i < content.size(); ++i) {
        cout << "[" << i + 1 << "] " << content[i] << endl;
    }
    cout << endl;
}

int main() {
    string read_filename = "input.txt";
    string write_filename = "output.txt";
    try {
        FileHandler reader(read_filename, ios::in);
        vector<string> content = reader.read_file();
        print_file_content(content);

        vector<string> new_content = {"=== 新写入的内容 ===", "原文件共" + to_string(content.size()) + "行", "这是第一行新内容", "这是第二行新内容"};
        FileHandler writer(write_filename, ios::out | ios::trunc);
        writer.write_file(new_content);

        FileHandler verify_reader(write_filename, ios::in);
        vector<string> verify_content = verify_reader.read_file();
        print_file_content(verify_content);
    } catch (const FileOpenException& e) {
        cout << "\n错误提示:" << e.what() << endl;
    } catch (const FileReadException& e) {
        cout << "\n错误提示:" << e.what() << endl;
    } catch (const FileWriteException& e) {
        cout << "\n错误提示:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "\n系统错误:" << e.what() << endl;
    } catch (...) {
        cout << "\n未知错误:发生未预期的异常" << endl;
    }
    return 0;
}
结论

该文件工具类通过自定义异常类提供了详细的错误信息,基于 RAII 模式保证了文件资源的正确释放,即使发生异常也不会导致资源泄漏,同时通过分层捕获异常,为用户提供了友好的提示,符合异常处理的最佳实践。

总结

异常处理是 C++ 处理运行时错误的核心机制,通过 try-catch-throw 实现错误检测与处理的分离。标准异常库提供了一系列预定义异常类,自定义异常类应继承自 std::exception,重写 what() 方法。异常的匹配遵循精确匹配、派生类匹配规则,catch 块需按派生类在前、基类在后的顺序声明。异常安全是关键,需通过智能指针、RAII 模式管理资源。最佳实践包括明确异常使用场景、按引用捕获异常、记录异常信息、避免在析构函数中抛出异常。

目录

  1. C++ 异常处理机制:捕获、自定义与实战
  2. 核心目标
  3. 异常处理概述
  4. 什么是异常处理
  5. 为什么需要异常处理
  6. C++ 异常处理的核心组件
  7. 异常处理基本语法与执行流程
  8. 基本语法格式
  9. 执行流程详解
  10. 异常的匹配规则
  11. 标准异常库
  12. 自定义异常类
  13. 自定义异常类的实现
  14. 异常处理的高级特性
  15. 异常规格说明与 noexcept
  16. 异常的传播与重新抛出
  17. 异常安全
  18. 异常处理的常见错误与最佳实践
  19. 常见错误
  20. 最佳实践
  21. 实战案例:文件读写的异常处理
  22. 问题描述
  23. 实现思路
  24. 代码实现
  25. 结论
  26. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • WebGPU 全面解析:新一代 Web 图形与计算 API
  • 从工具焦虑到持续变现:AI 赚钱的关键不在技术
  • Axum Rust Web 框架核心使用指南
  • Spring Boot RESTful API 分层开发与测试实战
  • 基于 Docker 的智能家居音乐播放系统部署指南
  • Ubuntu 系统下 libwebkit2gtk-4.1-0 库安装指南
  • FASTLIVO2 算法解析与实战(一):SLAM 系统架构与核心模块
  • 鸿蒙电商购物车全栈实战:用户管理、商品列表与购物车实现
  • OpenClaw 安装部署全流程:搭建自托管 AI 助手
  • Python 中 GraphQL 的实战指南:从 Schema 设计到性能优化
  • 字符串模拟题精选:思维与实现解析
  • AMD 显卡本地部署 AI 大模型实战指南
  • Java 核心基础:语言特点、环境架构与八种基本数据类型详解
  • 双延迟深度确定性策略梯度算法 (TD3) 详解与实现
  • GESP C++ 一级认证真题与解析:单选题 1-15
  • Python 正则表达式详解与实战案例
  • 35 道常见前端 Vue 面试题解析
  • Dify 集成 MySQL 实战:基于 MCP 协议的数据查询方案
  • Python 就业数据分析:方向、岗位与城市选择指南
  • Neo4j 图数据库安装与基础使用指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • 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