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

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

深入解析 C++ 异常处理机制,涵盖 try-catch-throw 语法流程、标准异常库使用及自定义异常类设计。重点讲解了异常匹配规则、noexcept 关键字优化、异常安全原则(RAII 与智能指针)以及常见错误规避。通过文件读写实战案例,演示了如何构建健壮的容错代码,确保资源正确释放与状态一致性。

KernelLab发布于 2026/3/15更新于 2026/7/2934 浏览
C++ 异常处理机制:异常捕获、自定义异常与实战应用

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

在这里插入图片描述

学习目标与重点

咱们今天聊聊 C++ 里怎么优雅地处理错误。核心目标就几个:搞懂 try-catch-throw 的底层逻辑,学会自己造异常类,还有在实际开发里怎么避免内存泄漏和状态不一致。特别是 noexcept 关键字和 RAII 模式,这可是提升代码健壮性的关键。

💡 核心重点:try-catch 的匹配顺序、自定义异常的继承设计、异常安全原则以及实战中的策略选择。

异常处理概述

什么是异常处理

简单说,异常处理就是把'发现错误'和'解决错误'分开。程序跑着跑着遇到坑(比如除以零),直接抛个异常出去,让专门负责兜底的模块去处理,而不是让程序直接崩掉。

🗄️ 打个比方:

  • 快递配送:快递员发现地址错了(异常),不会把货扔路边,而是上报系统(抛出),客服介入联系收件人(处理)。
  • 餐厅点餐:厨师发现没菜了,不直接拒单,告诉服务员(抛出),服务员跟顾客解释并推荐别的(处理)。

为什么需要异常处理

以前我们习惯用返回值判断错误,但这玩意儿缺陷挺多:

// 传统方式:通过返回值判断
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 本身是合法的),每层函数都得检查返回值,代码冗余还容易漏。异常处理的优势在于错误检测和处理分离,异常可以跨层级传播,还能携带丰富的错误信息。

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

主要靠三个关键字配合:

  1. throw:检测到错时触发抛出。
  2. try:包裹可能出错的代码块。
  3. catch:捕获并处理特定类型的异常。

流程大概是:try 里执行 -> 出错 throw -> 跳到最近的匹配 catch -> 处理完继续往下走。

基本语法与执行流程

基本语法格式

try {
    // 可能抛出异常的代码块
    if (错误条件) {
        throw 异常值; // 可以是 int、string 或自定义类
    }
} catch (异常类型 1 异常变量) {
    // 处理类型 1
} catch (异常类型 2 异常变量) {
    // 处理类型 2
} catch (...) {
    // 兜底捕获所有未匹配的异常
}

这里要注意,try 块后面必须紧跟至少一个 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!
程序继续执行...

流程拆解一下:程序进 try 调用 divide,发现 b=0 就 throw 字符串,main 里的第一个 catch 匹配成功,打印信息,然后跳过后续代码,从 catch 块后面继续跑。

异常的匹配规则

catch 是按声明顺序匹配的。这里有几个坑:

  1. 精确匹配:类型完全一致。
  2. 派生类匹配:基类 catch 能抓派生类异常。
  3. 顺序很重要:如果先把基类 catch 放前面,派生类的 catch 永远拿不到机会。
// 错误示范:基类在前
try {
    throw DerivedException();
} catch (BaseException& e) { // 先抓到基类,下面的 never run
    cout << "基类异常" << endl;
} catch (DerivedException& e) {
    cout << "派生类异常" << endl;
}

所以记住:派生类在前,基类在后。

标准异常库

C++ 标准库提供了一套现成的异常类,都在 <exception> 里,都继承自 std::exception。常用的有:

异常类描述适用场景
std::exception基类兜底捕获
std::logic_error逻辑错误无效参数、非法状态
std::invalid_argument无效参数传参不对
std::out_of_range越界数组下标、string 索引
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 << nums.at(3) << endl; // at() 会抛异常
    } 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 风格字符串,适合做日志或提示。

自定义异常类

标准异常不够用时,就得自己写。比如业务上的'用户不存在'、'权限不足'。

设计原则

  1. 继承自 std::exception 或其子类。
  2. 重写 what() 方法返回描述。
  3. 构造函数带上错误信息。
  4. 类名要清晰,一眼看出是什么异常。

实现示例

#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;
}

这样的好处是语义清晰,层次分明,而且兼容标准异常体系。

⚠️ 注意:what() 必须是 const noexcept,符合基类规范。异常对象尽量轻量,因为抛出时会拷贝。捕获时优先用引用 catch (const Exception& e)。

高级特性

noexcept 关键字

C++11 之前有 throw(...) 规格说明,但现在废了,推荐用 noexcept。

void func() noexcept { /* 保证不抛异常 */ }
void func2() noexcept(false) { /* 明确可能抛异常 */ }

作用有三:编译器优化(省栈展开代码)、明确接口契约、影响标准库行为(比如 vector 移动构造)。

⚠️ 警告:noexcept 函数如果真抛了异常,程序直接 terminate(),没法 catch。

异常传播与重新抛出

异常可以在函数间传播。如果当前层处理不了,可以用 throw; 重新抛给上层。

void process_data(int data) {
    if (data < 0) throw string("数据非法");
}

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

异常安全

核心就是:抛出异常时,别泄露内存,别留脏数据。

  1. 智能指针:unique_ptr、shared_ptr 自动管理内存。
  2. RAII:资源获取即初始化,析构时释放。
#include <memory>
#include <string>

void safe_func() {
    unique_ptr<int> p(new int(10));
    throw string("测试异常安全"); // 即使抛异常,p 也会析构
}

文件操作也是同理,封装成类,析构时关闭文件流。

常见错误与最佳实践

常见错误

  1. 过度使用:别把正常逻辑当异常处理(比如找元素没找到就抛异常)。
  2. 吞掉异常:catch (...) 什么都不干,问题排查会死得很惨。
  3. 按值捕获:catch (BaseException e) 会导致对象切片,丢失派生类信息。
  4. 析构抛异常:析构函数里别抛异常,否则可能导致程序终止。

最佳实践

  • 异常情况才用异常,正常控制流用返回值。
  • 优先用标准异常或继承 std::exception 的自定义类。
  • 按引用捕获 (const &)。
  • catch 顺序:派生类在前,基类在后,兜底在最后。
  • 重要操作要有回滚机制。
  • 记录详细日志(类型、位置、堆栈)。

实战案例:文件读写

来个综合点的例子,做个文件工具类,支持读写,带异常处理和 RAII。

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

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, "无法打开文件");
        }
        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 错误");
        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, "刷新失败");
    }
};

int main() {
    string read_filename = "input.txt";
    string write_filename = "output.txt";
    try {
        FileHandler reader(read_filename, ios::in);
        auto content = reader.read_file();
        
        vector<string> new_content = {"新内容 1", "新内容 2"};
        FileHandler writer(write_filename, ios::out | ios::trunc);
        writer.write_file(new_content);
    } 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 确保文件句柄在异常发生时也能正确关闭,避免了资源泄漏。

总结

异常处理是 C++ 健壮性的基石。通过 try-catch-throw 分离关注点,结合标准异常库和自定义类,再配合 noexcept 和 RAII 模式,能有效应对各种运行时错误。记住几个关键点:派生类 catch 在前,按引用捕获,资源管理交给智能指针或 RAII,别在析构函数里抛异常。把这些用好,你的代码质量能上一个大台阶。

目录

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

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

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

更多推荐文章

查看全部
  • whisperX 入门指南:从安装到实现语音识别功能
  • Claude Code 高级编程技巧与实战项目详解
  • 开源 RAG 引擎 RAGFlow 部署与实战指南
  • C++ 实现 Wishart 分布样本矩阵生成
  • Java 微服务架构设计模式:构建云原生分布式系统
  • JS关闭当前网页
  • CSS3 十六进制透明度实战:#RRGGBBAA 用法与避坑指南
  • 基于 JeecgBoot 低代码平台构建请假审批系统实战
  • 本地部署 AI 大模型的电脑硬件配置指南
  • 基于微服务架构的智能家居物联网平台实现
  • 基于 Python 的轻量级上位机开发:流程与核心逻辑
  • Google AI Pro 订阅评测与核心权益功能解析
  • OpenClaw 接入 QVeris 实现 AI 助手实时数据查询
  • CentOS 安装 epel-release 遇 SyntaxError 错误排查与修复
  • 将 AI 小助手接入企业微信:基于回调接口的群聊机器人实现
  • 汽车雷达多径效应下的幽灵目标检测技术解析
  • C++ 静态成员与非静态成员详解
  • QClaw 实战指南:一周体验本地 AI 代理
  • OpenClaw 接入自定义模型并通过 WebUI 实现智能操作
  • ipget:基于 IPFS 的零配置分布式文件下载工具

相关免费在线工具

  • 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