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

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

C++ 异常处理通过 try-catch-throw 机制实现错误检测与处理的分离。文章详解了标准异常库的使用、自定义异常类的继承设计原则,以及 noexcept 关键字在接口契约中的作用。重点阐述了异常安全策略,包括智能指针与 RAII 模式在资源管理中的应用,并通过文件读写实战案例展示了如何构建健壮的容错系统。遵循最佳实践可有效避免内存泄漏与程序崩溃。

云间运维发布于 2026/3/22更新于 2026/7/3040 浏览
C++ 异常处理机制:异常捕获、自定义异常与实战应用

异常处理概述

异常处理是 C++ 中处理程序运行时错误的核心机制,其本质在于将错误检测与错误处理分离。当程序在运行过程中遇到如除以零、内存分配失败等问题时,可以在出错点抛出异常,然后在合适的层级(如主函数或业务逻辑层)捕获并处理,从而避免程序直接崩溃,提升代码的健壮性。

为什么需要异常处理

在引入异常处理之前,程序通常依赖返回值来判断是否出错。这种方式存在明显缺陷:

  1. 返回值冲突:例如除法函数返回 -1 可能表示错误,但也可能是合法的计算结果。
  2. 调用链冗余:多层函数调用时,必须逐层传递错误状态,代码冗余且容易遗漏检查。
  3. 错误传播困难:深层的错误难以传递到顶层进行统一处理。

相比之下,异常处理的优势在于错误检测与处理解耦,异常可以跨函数、跨层级自动传播,并能携带丰富的错误信息(如类型、原因、位置),有效防止小错误导致系统崩溃。

核心组件与流程

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

  • throw:检测到错误时抛出异常对象。
  • try:包裹可能抛出异常的代码块,定义异常检测范围。
  • catch:捕获并处理匹配的异常类型。

基本流程是:try 块执行代码 → 若出错则 throw 抛出异常 → 程序跳转至最近的匹配 catch 块 → 执行处理后继续向下运行。

基本语法与执行流程

语法结构

try {
    // 可能抛出异常的代码
    if (错误条件) {
        throw 异常值; // 可抛出任意类型:int, string, 自定义类等
    }
} catch (异常类型 变量名) {
    // 处理特定类型异常
} catch (...) {
    // 兜底捕获所有未匹配异常
}

注意 try 块不能单独存在,必须紧跟至少一个 catch 块。catch(...) 作为万能捕获,必须放在最后。

执行流程示例

下面是一个除数为零的异常处理示例,展示了异常如何中断当前执行流并跳转:

#include <iostream>
using namespace std;

int divide(int a, int b) {
    if (b == 0) {
        throw string();
    }
     a / b;
}

{
     x = , y = ;
     {
        cout <<  << endl;
         result = (x, y);
        cout << x <<  << y <<  << result << endl;
    }  ( string& err_msg) {
        cout <<  << err_msg << endl;
    }  (...) {
        cout <<  << endl;
    }
    cout <<  << endl;
     ;
}
"错误:除数不能为 0!"
return
int main()
int
10
0
try
"尝试执行除法运算..."
int
divide
" / "
" = "
catch
const
"捕获到异常:"
catch
"捕获到未知异常!"
"程序继续执行..."
return
0

预期输出如下:

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

这里可以看到,一旦 divide 函数抛出异常,main 函数中后续的打印语句被跳过,控制权直接交给 catch 块。处理完成后,程序从 catch 块之后继续运行。

异常匹配规则

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

  1. 精确匹配:类型完全一致。
  2. 派生类匹配:抛出的派生类异常可被基类类型的 catch 捕获。
  3. 类型转换:仅支持有限的隐式转换。
  4. 兜底捕获:catch(...) 匹配剩余所有异常,必须置于最后。

⚠️ 警告:catch 块的顺序至关重要。如果基类异常捕获放在派生类之前,派生类异常将被基类捕获,导致后续代码无法执行。

标准异常库与自定义异常

标准异常类

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

异常类描述
std::exception所有标准异常的基类
std::logic_error逻辑错误(如无效参数)
std::runtime_error运行时错误(如 IO 失败)
std::bad_alloc内存分配失败

使用 what() 方法可以获取异常的简要描述字符串。

自定义异常类设计

实际开发中,我们常需要业务相关的异常(如'用户不存在'、'权限不足')。自定义异常类应遵循以下原则:

  1. 继承自 std::exception 或其派生类。
  2. 重写 what() 方法返回详细描述。
  3. 提供必要的构造函数。
#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;
}

这样设计的优势在于语义清晰、层次分明,且能兼容标准异常的统一处理逻辑。

高级特性与异常安全

noexcept 关键字

C++11 引入了 noexcept 关键字,用于声明函数是否可能抛出异常。相比旧的异常规格说明,它更简洁且能被编译器优化。

void func() noexcept { /* 保证不抛异常 */ }

若 noexcept 函数内部抛出异常,程序会直接终止,因此需确保该函数确实不会抛出异常。此外,noexcept 还能影响标准库行为,例如 std::vector 的移动构造若标记为 noexcept 会使用移动语义而非拷贝。

异常传播与重新抛出

异常抛出后若未被当前函数捕获,会自动向上传播至调用者。若需在 catch 块中处理部分逻辑(如记录日志)后继续向上抛出,可使用 throw;(不带参数)。

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

异常安全策略

异常安全要求程序在抛出异常时不发生内存泄漏、数据状态不一致或资源未释放。主要解决方案包括:

  1. 智能指针:利用 std::unique_ptr 等自动管理内存。
  2. RAII 模式:资源获取即初始化,通过析构函数自动释放资源。
  3. 标准容器:优先使用 vector、string 等具备异常安全的组件。
RAII 实战示例

下面是一个基于 RAII 的文件读写工具类,确保即使发生异常文件也能正确关闭:

#include <fstream>
#include <string>
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("模拟写入过程中异常"); // 触发异常
}

int main() {
    try {
        write_file("test.txt", "Hello");
    } catch (const string& e) {
        cout << "捕获异常:" << e << endl;
    }
    return 0;
}

即使 write_file 中途抛出异常,FileGuard 对象的析构函数也会被调用,确保文件句柄被正确释放,避免了资源泄漏。

常见错误与最佳实践

常见陷阱

  1. 过度使用异常:异常应用于异常情况,不应替代正常的控制流(如循环查找元素)。
  2. 捕获所有异常却不处理:catch(...) 若无日志记录,会导致问题难以排查。
  3. 按值捕获异常:应使用引用 catch (const Exception& e),避免对象切片和拷贝开销。
  4. 析构函数抛出异常:析构函数中抛出异常可能导致程序终止,应避免。

最佳实践建议

  • 明确场景:仅在真正的错误(如 IO 失败、非法参数)时使用异常。
  • 统一规范:系统级错误用标准异常,业务级错误用自定义异常。
  • 资源管理:始终使用智能指针或 RAII 管理资源。
  • 日志记录:捕获异常后记录详细信息(类型、堆栈、上下文)。

实战案例:文件读写工具

结合上述知识,我们可以构建一个健壮的 FileHandler 类,支持文件读写并处理各类异常。

#include <iostream>
#include <fstream>
#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) {}
};

class FileReadException : public FileException {
public:
    FileReadException(const string& filename, const string& reason)
        : FileException(filename, "读取失败 - " + reason) {}
};

class FileWriteException : public FileException {
public:
    FileWriteException(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;
        if (!file_stream.good()) {
            throw FileReadException(filename, "文件流状态异常");
        }
        while (getline(file_stream, line)) {
            content.push_back(line);
        }
        if (file_stream.bad()) {
            throw FileReadException(filename, "IO 错误");
        }
        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();
    }
};

int main() {
    try {
        FileHandler reader("input.txt", ios::in);
        auto content = reader.read_file();
        
        FileHandler writer("output.txt", ios::out | ios::trunc);
        writer.write_file({"新内容 1", "新内容 2"});
    } catch (const FileOpenException& e) {
        cout << "错误提示:" << e.what() << endl;
    } catch (const FileReadException& e) {
        cout << "错误提示:" << e.what() << endl;
    } catch (const FileWriteException& e) {
        cout << "错误提示:" << e.what() << endl;
    } catch (const exception& e) {
        cout << "系统错误:" << e.what() << endl;
    }
    return 0;
}

该案例展示了如何通过分层异常类和 RAII 模式,在保证资源安全的前提下,为用户提供清晰的错误反馈。

目录

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

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

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

更多推荐文章

查看全部
  • 网络安全入门:黑客必备技术与知识体系
  • HTTP 请求方式详解:GET、POST 及其他常用方法
  • Flutter 使用 web3dart 连接以太坊构建 DApp 及 OpenHarmony 适配实战
  • WorkBuddy 接入 QQ 机器人配置指南
  • WorkBuddy 接入 QQ 机器人配置指南
  • Visual Studio 2026 Insider C++ 项目属性推荐配置
  • Jetson 部署 OpenClaw 接入飞书机器人实现本地 AI 助手
  • AI 辅助 Python 毕业设计项目架构搭建与实现
  • AI 图像生成指南:从原理到实战
  • AI Agent 核心在于流程设计:Harness 才是决胜关键
  • Spring AI 会话记忆多存储与提示词工程实战
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具实战
  • Github Copilot 学生认证重新通过方法(含 2FA 及材料准备)
  • 无人机光伏巡检:低空经济下的运维革新与实战
  • C++ 使用 LibreHardwareMonitor 库实现硬件监控
  • 机器人数值优化基础(一)从理论到实战
  • 基于 AI Ping 的大模型 API 统一接入与成本优化实践
  • LLM 大模型量化技术、QLoRA 及常用量化库详解
  • JVM 垃圾回收机制:可达性分析算法详解
  • ChatTTS 开源语音模型部署与使用指南

相关免费在线工具

  • 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