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

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

C++ 异常处理通过 try-catch-throw 实现错误检测与处理分离。文章详解标准异常库使用、自定义异常类设计原则及继承体系。涵盖 noexcept 关键字优化、异常传播与重新抛出机制。重点讲解 RAII 模式与智能指针在保障异常安全中的应用,避免内存泄漏。结合文件读写实战案例,展示分层捕获策略与最佳实践,提升代码健壮性。

flc发布于 2026/3/24更新于 2026/5/2215 浏览
C++ 异常处理机制:异常捕获、自定义异常与实战应用

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

导读

本文旨在深入探讨 C++ 中的异常处理机制。我们将掌握核心概念(异常、抛出、捕获、处理)及基本语法,理解 try-catch-throw 语句的执行流程,学会自定义异常类以满足实际开发需求,并掌握最佳实践以规避内存泄漏等常见错误。

学习目标

  • 掌握异常处理的核心概念及基本语法
  • 理解 try-catch-throw 语句的执行流程
  • 学会自定义异常类,满足个性化场景需求
  • 掌握异常处理的最佳实践,规避常见错误
  • 理解 noexcept 关键字的使用场景
  • 结合实战案例,提升代码的健壮性和容错能力

一、异常处理概述

1.1 什么是异常处理

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

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

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

1.2 为什么需要异常处理

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

// 传统错误处理:通过返回值判断
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;
}

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

异常处理的优势在于:错误检测与处理分离,代码结构清晰;异常可跨函数、跨层级传播;可携带丰富的错误信息;避免程序因小错误直接崩溃。

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

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

  1. throw:抛出异常(检测到错误时触发)
  2. try:尝试执行可能抛出异常的代码块
  3. catch:捕获并处理异常

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

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

2.1 基本语法格式

try {
    // 可能抛出异常的代码块
    if (错误条件) {
        throw 异常值; // 抛出任意类型:int、string、自定义类等
    }
} catch (异常类型 1 异常变量) {
    // 处理异常类型 1 的逻辑
} catch (异常类型 2 异常变量) {
    // 处理异常类型 2 的逻辑
} catch (...) {
    // 捕获所有未匹配的异常(兜底处理)
}
  • try 块必须紧跟一个或多个 catch 块。
  • throw 表达式抛出后立即终止当前函数执行。
  • catch 块按顺序匹配异常类型,catch (...) 需放在最后。

2.2 执行流程详解

以下示例演示了基本的异常处理流程(除数为 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!
程序继续执行...

流程拆解:程序进入 try 块 → 调用 divide 函数 → 检测到 b=0 抛出 string 异常 → 跳转至 main 函数中最近的 catch 块 → 打印错误信息 → 程序继续运行。

2.3 异常的匹配规则

catch 块按声明顺序匹配异常类型:

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

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

2.4 标准异常库

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

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

使用标准异常类的示例:

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

int main() {
    vector<int> nums = {1, 2, 3};
    try {
        cout << "访问索引 3 的元素:" << nums.at(3) << endl;
    } catch (const out_of_range& e) {
        cout << "捕获到 out_of_range 异常:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "捕获到标准异常:" << e.what() << endl;
    }
    return 0;
}

技巧:标准异常类的 what() 方法返回 C 风格字符串,可用于日志输出或用户提示。

三、自定义异常类

标准异常类虽能满足常见场景,但实际开发中常需要自定义异常(如业务相关的'用户不存在异常')。

3.1 自定义异常的设计原则

  1. 继承自标准异常类(推荐 std::exception 或其派生类)。
  2. 重写 what() 方法,返回自定义的异常描述信息。
  3. 提供必要的构造函数。
  4. 异常类名清晰,体现异常类型。

3.2 自定义异常类的实现

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

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

// 基础业务异常类
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();
    }
};

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

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

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

注意事项:

  1. what() 方法必须重写为 const noexcept。
  2. 异常类应尽量轻量,避免复杂的成员变量。
  3. 优先使用引用捕获异常,避免拷贝开销。

四、异常处理的高级特性

4.1 异常规格说明与 noexcept

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

void func() noexcept { /* 不会抛出异常 */ }
void func2() noexcept(false) { /* 可能抛出异常 */ }

noexcept 的核心作用:编译器优化(省略异常处理代码)、明确接口契约、影响标准库行为(如移动语义)。

⚠️ 警告:若 noexcept 函数实际抛出了异常,程序会调用 std::terminate() 终止。

4.2 异常的传播与重新抛出

4.2.1 异常的跨函数传播

异常抛出后,若当前函数没有匹配的 catch 块,异常会向上传播到调用该函数的上层函数。

4.2.2 异常的重新抛出

有时需要在 catch 块中处理部分逻辑后,将异常重新抛出给上层函数处理,使用 throw; 实现。

void handle_request(int data) {
    try {
        process_data(data);
    } catch (const string& e) {
        cout << "日志记录:发生异常 - " << e << endl;
        throw; // 重新抛出原始异常对象
    }
}

4.3 异常安全

异常安全是指程序抛出异常时,确保不发生内存泄漏、数据状态一致、资源被正确释放。

4.3.1 常见的异常安全问题
// 异常安全问题:内存泄漏
void unsafe_func() {
    int* p = new int(10);
    process_data(-5); // 可能抛出异常
    delete p;         // 若抛出异常,此句不执行,内存泄漏
}
4.3.2 异常安全的解决方案
  1. 使用智能指针:自动释放内存。
  2. RAII 模式:资源获取即初始化,利用生命周期管理资源。
  3. 使用容器和标准库组件:具备异常安全性。

示例:使用 RAII 模式管理文件资源。

#include <fstream>
using namespace std;

class FileGuard {
private:
    ofstream file;
public:
    FileGuard(const string& filename) : file(filename) {
        if (!file.is_open()) {
            throw string("文件打开失败:" + filename);
        }
    }
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
        }
    }
    void write(const string& content) {
        file << content << endl;
    }
};

void write_file(const string& filename, const string& content) {
    FileGuard file(filename);
    file.write(content);
    throw string("模拟写入过程中异常");
    // 异常抛出后,FileGuard 对象析构,文件自动关闭
}

五、常见错误与最佳实践

5.1 常见错误

  1. 过度使用异常:将异常用于正常的控制流。
  2. 捕获所有异常却不处理:导致问题排查困难。
  3. 抛出非异常类型的对象:导致异常处理不统一。
  4. 异常对象切片:按值捕获异常导致派生类特有信息丢失。

5.2 最佳实践

  1. 明确异常使用场景:仅在异常情况使用异常。
  2. 优先使用标准异常或自定义异常类:继承自 std::exception。
  3. 按引用捕获异常:避免拷贝开销和对象切片。
  4. 合理组织 catch 块顺序:派生类在前,基类在后。
  5. 保证异常安全:使用智能指针和 RAII 模式。
  6. 记录异常信息:便于问题排查。
  7. 避免在析构函数中抛出异常:可能导致程序终止。

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

6.1 问题描述

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

6.2 代码实现

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

// 自定义文件异常基类
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();
    }
};

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

// 文件工具类(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, "无法打开文件");
        }
    }
    ~FileHandler() {
        if (file_stream.is_open()) {
            file_stream.close();
        }
    }
    vector<string> read_file() {
        vector<string> content;
        string line;
        while (getline(file_stream, line)) {
            content.push_back(line);
        }
        return content;
    }
    void write_file(const vector<string>& content) {
        for (const string& line : content) {
            file_stream << line << endl;
        }
        file_stream.flush();
    }
};

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();
        
        FileHandler writer(write_filename, ios::out | ios::trunc);
        vector<string> new_content = {"=== 新写入的内容 ===", "这是第一行新内容"};
        writer.write_file(new_content);
    } catch (const FileOpenException& e) {
        cout << "错误提示:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "系统错误:" << e.what() << endl;
    }
    return 0;
}

✅ 结论:该文件工具类通过自定义异常类提供了详细的错误信息,基于 RAII 模式保证了文件资源的正确释放,符合异常处理的最佳实践。

七、总结

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

通过本文学习,你应能熟练运用异常处理机制解决实际开发中的错误处理问题,编写健壮、可靠的 C++ 代码。

目录

  1. C++ 异常处理机制:异常捕获、自定义异常与实战应用
  2. 导读
  3. 学习目标
  4. 一、异常处理概述
  5. 1.1 什么是异常处理
  6. 1.2 为什么需要异常处理
  7. 1.3 C++ 异常处理的核心组件
  8. 二、异常处理基本语法与执行流程
  9. 2.1 基本语法格式
  10. 2.2 执行流程详解
  11. 2.3 异常的匹配规则
  12. 2.4 标准异常库
  13. 三、自定义异常类
  14. 3.1 自定义异常的设计原则
  15. 3.2 自定义异常类的实现
  16. 四、异常处理的高级特性
  17. 4.1 异常规格说明与 noexcept
  18. 4.2 异常的传播与重新抛出
  19. 4.2.1 异常的跨函数传播
  20. 4.2.2 异常的重新抛出
  21. 4.3 异常安全
  22. 4.3.1 常见的异常安全问题
  23. 4.3.2 异常安全的解决方案
  24. 五、常见错误与最佳实践
  25. 5.1 常见错误
  26. 5.2 最佳实践
  27. 六、实战案例:文件读写的异常处理
  28. 6.1 问题描述
  29. 6.2 代码实现
  30. 七、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • LLaMA-Factory 大模型微调技术背景与流程
  • Nix 入门教程:5 步创建可复现开发环境
  • 基于改进 YOLOv11n 的无人机红外目标检测算法
  • 2026 年最新机器人系统架构与技术路线分析
  • C++ 二分查找算法模板及例题详解
  • Rust 异步测试与调试的实践指南
  • C++ Primer 中文版电子书简介
  • Bing Webmaster 工具使用指南:网站验证与收录提交
  • Linux 网络:理解 Web 路径及实现 Hello World 服务
  • Seedream 4.0 图像生成模型功能解析与创意玩法
  • 基于FPGA的USB2.0 UTMI PHY芯片测试方案设计与实现
  • 基于 RK3588 开发板的多传感信息融合多用途巡检机器人
  • AI 大模型旅游规划智能体 React Agent 实战
  • 使用 Trae IDE 和 MCP Server 将 Figma 设计稿自动转换为前端代码
  • 并发编程面试:乐观锁与悲观锁的区别及应用场景
  • Whisper 语音识别技术本地部署与应用指南
  • Python 自适应大邻域搜索(ALNS)算法教程
  • Cesium 无人机智能航线规划:航点动作组与 AI 识别实战
  • ibbot(智体机灵):国产开源AI智能体平台解析
  • AI 与存储的结合:智能存储的实践与挑战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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