C++ 异常处理机制
一、异常处理的核心概念
异常处理是 C++ 提供的一种错误处理范式,核心思想是:将错误检测(抛出异常)和错误处理(捕获异常)分离,让正常业务逻辑和错误处理逻辑解耦。
核心关键字
表格
| 关键字 | 作用 |
|---|---|
throw | 抛出异常(检测到错误时,主动触发异常) |
try | 包裹可能抛出异常的代码块(监控代码) |
catch | 捕获并处理指定类型的异常(处理错误) |
二、基本用法(入门示例)
先看一个最简单的完整示例,理解try-throw-catch的协作关系:
cpp
运行
#include <iostream> #include <stdexcept> // 标准异常类头文件 using namespace std; // 定义一个可能抛出异常的函数 int divide(int a, int b) { if (b == 0) { // 抛出异常:可以是内置类型、自定义类型,推荐用标准异常类 throw runtime_error("除数不能为0"); } return a / b; } int main() { int x = 10, y = 0; // 1. try块:监控可能抛出异常的代码 try { int result = divide(x, y); // 调用可能抛异常的函数 cout << "计算结果:" << result << endl; // 无异常时执行 } // 2. catch块:捕获指定类型的异常(这里捕获runtime_error类型) catch (const runtime_error& e) { // 处理异常:打印错误信息,程序不会崩溃 cerr << "捕获到异常:" << e.what() << endl; } // 3. 通用catch(可选):捕获所有类型的异常 catch (...) { cerr << "捕获到未知异常" << endl; } cout << "程序继续执行..." << endl; // 异常处理后,程序正常往下走 return 0; }运行结果:
plaintext
捕获到异常:除数不能为0 程序继续执行...关键解释:
throw:当检测到除数为 0 时,用throw抛出一个runtime_error类型的异常(C++ 标准异常类,what()方法返回错误描述);try:divide(x, y)被包裹在try块中,一旦这个函数抛出异常,try块会立即终止执行,跳转到匹配的catch块;catch (const runtime_error& e):精准捕获runtime_error类型的异常,通过引用接收异常对象(避免拷贝,效率更高);catch (...):万能捕获,能捕获所有类型的异常(建议放在最后,作为兜底);- 异常处理完成后,程序会从最后一个
catch块之后继续执行(不会回到try块中抛出异常的位置)。
三、C++ 标准异常体系(推荐使用)
C++ 提供了一套标准化的异常类层级,所有标准异常都继承自exception类(定义在<exception>头文件),推荐优先使用标准异常,而非自定义裸类型(如int/string),提升代码规范性。
常用标准异常类(核心):
表格
| 异常类 | 头文件 | 适用场景 |
|---|---|---|
exception | <exception> | 所有标准异常的基类 |
runtime_error | <stdexcept> | 运行时错误(如除数为 0、数组越界) |
logic_error | <stdexcept> | 逻辑错误(如参数无效、前置条件不满足) |
out_of_range | <stdexcept> | 越界访问(如 vector::at () 越界) |
bad_alloc | <new> | 内存分配失败(如 new 申请内存失败) |
示例:使用标准异常子类
cpp
运行
#include <iostream> #include <stdexcept> using namespace std; void checkAge(int age) { if (age < 0 || age > 150) { // 逻辑错误:参数不符合规则 throw logic_error("年龄必须在0-150之间"); } cout << "年龄合法:" << age << endl; } int main() { try { checkAge(-5); // 触发logic_error异常 } // 捕获logic_error(继承自exception) catch (const logic_error& e) { cerr << "逻辑错误:" << e.what() << endl; } // 捕获所有标准异常(基类) catch (const exception& e) { cerr << "标准异常:" << e.what() << endl; } return 0; }四、自定义异常类(进阶)
如果标准异常无法满足需求(比如需要携带更多错误信息),可以继承exception或其派生类,自定义异常:
cpp
运行
#include <iostream> #include <exception> #include <string> using namespace std; // 自定义异常类:继承exception,重写what()方法 class MyException : public exception { private: string msg; // 自定义错误信息 public: // 构造函数:初始化错误信息 MyException(const string& message) : msg(message) {} // 重写what():返回错误描述(必须是const char*) const char* what() const noexcept override { return msg.c_str(); } }; // 测试自定义异常 void testCustomException() { throw MyException("自定义异常:业务规则校验失败"); } int main() { try { testCustomException(); } catch (const MyException& e) { cerr << "捕获自定义异常:" << e.what() << endl; } return 0; }五、异常处理的关键规则
- 异常匹配规则:
catch块按从上到下的顺序匹配,子类异常要放在父类异常前面(比如logic_error要在exception前面),否则子类异常会被父类catch捕获,导致匹配不到; - 异常的传播:如果
try块内的函数抛出异常,且当前catch没有匹配的类型,异常会向上传播(比如从函数传播到调用者,再到 main 函数),如果最终没有被捕获,程序会调用terminate()终止(崩溃); - noexcept 关键字:修饰函数,表示该函数不会抛出异常(C++11 及以上),如果函数违背承诺抛出异常,程序会直接终止:
cpp
运行
// 声明该函数不会抛出异常 int safeFunction() noexcept { return 100; } - 异常与资源管理:如果异常抛出时,程序已申请资源(如堆内存、文件句柄),需要手动释放,或使用 RAII(如智能指针)自动释放,避免内存泄漏。
六、异常处理的优缺点
表格
| 优点 | 缺点 |
|---|---|
| 错误处理逻辑与业务逻辑分离,代码更清晰 | 少量性能开销(异常抛出时的栈展开) |
| 可以跨函数、跨模块传递错误信息 | 滥用会导致代码可读性下降(异常路径不直观) |
| 避免程序直接崩溃,提升鲁棒性 | 需注意资源释放,否则易内存泄漏 |
总结
- C++ 异常处理核心是
try(监控)、throw(抛出)、catch(捕获),实现错误检测与处理分离; - 优先使用 C++ 标准异常类(如
runtime_error/logic_error),自定义异常需继承exception并重写what(); - 关键规则:
catch块按 “子类在前、父类在后” 匹配,未捕获的异常会导致程序崩溃,noexcept修饰函数表示不抛异常。
