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

学习目标与重点
咱们今天聊聊 C++ 里怎么优雅地处理错误。核心目标就几个:搞懂 try-catch-throw 的底层逻辑,学会自己造异常类,还有在实际开发里怎么避免内存泄漏和状态不一致。特别是 noexcept 关键字和 RAII 模式,这可是提升代码健壮性的关键。
💡 核心重点:try-catch 的匹配顺序、自定义异常的继承设计、异常安全原则以及实战中的策略选择。
异常处理概述
什么是异常处理
简单说,异常处理就是把'发现错误'和'解决错误'分开。程序跑着跑着遇到坑(比如除以零),直接抛个异常出去,让专门负责兜底的模块去处理,而不是让程序直接崩掉。
🗄️ 打个比方:
- 快递配送:快递员发现地址错了(异常),不会把货扔路边,而是上报系统(抛出),客服介入联系收件人(处理)。
- 餐厅点餐:厨师发现没菜了,不直接拒单,告诉服务员(抛出),服务员跟顾客解释并推荐别的(处理)。
为什么需要异常处理
以前我们习惯用返回值判断错误,但这玩意儿缺陷挺多:
int divide(int a, int b) {
if (b == 0) {
return -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++ 异常处理的核心组件
throw:检测到错时触发抛出。
try:包裹可能出错的代码块。
catch:捕获并处理特定类型的异常。
流程大概是:try 里执行 -> 出错 throw -> 跳到最近的匹配 catch -> 处理完继续往下走。
基本语法与执行流程
基本语法格式
try {
if (错误条件) {
throw 异常值;
}
} catch (异常类型 1 异常变量) {
} catch (异常类型 2 异常变量) {
} catch (...) {
}
这里要注意,try 块后面必须紧跟至少一个 catch,不能单独存在。catch (...) 是万能捕获,记得放在最后,不然前面的会把它挡住。
执行流程详解
#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 能抓派生类异常。
- 顺序很重要:如果先把基类
catch 放前面,派生类的 catch 永远拿不到机会。
try {
throw DerivedException();
} catch (BaseException& e) {
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;
} 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 风格字符串,适合做日志或提示。
自定义异常类
标准异常不够用时,就得自己写。比如业务上的'用户不存在'、'权限不足'。
设计原则
- 继承自
std::exception 或其子类。
- 重写
what() 方法返回描述。
- 构造函数带上错误信息。
- 类名要清晰,一眼看出是什么异常。
实现示例
#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;
}
}
异常安全
- 智能指针:
unique_ptr、shared_ptr 自动管理内存。
- RAII:资源获取即初始化,析构时释放。
#include <memory>
#include <string>
void safe_func() {
unique_ptr<int> p(new int(10));
throw string("测试异常安全");
}
常见错误与最佳实践
常见错误
- 过度使用:别把正常逻辑当异常处理(比如找元素没找到就抛异常)。
- 吞掉异常:
catch (...) 什么都不干,问题排查会死得很惨。
- 按值捕获:
catch (BaseException e) 会导致对象切片,丢失派生类信息。
- 析构抛异常:析构函数里别抛异常,否则可能导致程序终止。
最佳实践
- 异常情况才用异常,正常控制流用返回值。
- 优先用标准异常或继承
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) {}
};
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,别在析构函数里抛异常。把这些用好,你的代码质量能上一个大台阶。
相关免费在线工具
- 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