什么是异常处理?

异常处理是C++中处理程序运行时错误的一种机制。想象一下你正在开车,突然前方出现障碍物 – 异常处理就像是你的应急系统,让你能够安全地处理突发情况,而不是直接让程序”撞车”崩溃。

在程序中,异常可能由多种原因引起:内存不足、文件不存在、除以零、无效输入等。异常处理机制允许我们检测这些错误并优雅地处理它们。

为什么需要异常处理?
  • 防止程序意外崩溃
  • 将错误处理代码与正常逻辑分离
  • 提供统一的错误处理机制
  • 使代码更清晰、更易维护

🔑 异常处理关键字

C++异常处理基于三个核心关键字:

try { // 尝试执行可能出错的代码 } catch (ExceptionType e) { // 处理特定类型的异常 } throw exception_object; // 抛出异常

关键字解释:

try:包裹可能抛出异常的代码块,相当于”尝试执行这段代码”。

catch:捕获并处理特定类型的异常,相当于”如果发生这种错误,就这样处理”。

throw:主动抛出一个异常,相当于”报告这里出错了”。

注意: 每个try块后面必须跟着一个或多个catch块,否则编译器会报错。

🚀 基本使用流程

让我们通过一个简单的例子理解异常处理的基本流程:

#include <iostream> using namespace std; int main() { try { int age = -5; // 如果年龄无效,抛出异常 if (age < 0) { throw “年龄不能为负数!”; } cout << “年龄是:” << age << endl; } catch (const char* errorMsg) { // 捕获字符串类型的异常 cerr << “错误:” << errorMsg << endl; } return 0; }

在这个例子中:

  1. 我们在try块中检查年龄是否小于0
  2. 如果小于0,使用throw抛出一个字符串异常
  3. catch块捕获这个异常并打印错误信息

🎯 多个catch块

一个try块后面可以跟多个catch块,用于捕获不同类型的异常:

try { // 可能抛出多种异常的代码 throw “发生了一个错误”; // 字符串异常 // 或者:throw 404; // 整数异常 // 或者:throw runtime_error(“运行时错误”); } catch (const char* msg) { cout << “字符串异常:” << msg << endl; } catch (int code) { cout << “错误代码:” << code << endl; } catch (const exception& e) { cout << “标准异常:” << e.what() << endl; } catch (…) { cout << “未知类型的异常” << endl; }

重要提示:

  • catch块的顺序很重要 – 异常会被第一个匹配类型的catch块捕获
  • catch(…) 是”通配符”,可以捕获所有类型的异常,通常放在最后
  • 尽量从具体到一般排列catch块

📦 标准异常类

C++标准库提供了许多预定义的异常类,都继承自std::exception:

#include <stdexcept> // 包含标准异常 try { // 尝试打开文件 ifstream file(“nonexistent.txt”); if (!file) { throw runtime_error(“文件打开失败!”); } // 尝试访问无效索引 vector<int> nums = {1, 2, 3}; if (nums.size() <= 5) { throw out_of_range(“索引超出范围!”); } } catch (const runtime_error& e) { cerr << “运行时错误:” << e.what() << endl; } catch (const out_of_range& e) { cerr << “范围错误:” << e.what() << endl; } catch (const exception& e) { cerr << “标准异常:” << e.what() << endl; }

常用标准异常:

  • logic_error:程序逻辑错误
  • runtime_error:运行时错误
  • invalid_argument:无效参数
  • out_of_range:超出有效范围
  • bad_alloc:内存分配失败

自定义异常

当标准异常不能满足需求时,可以创建自己的异常类:

// 自定义异常类 class MyException : public exception { private: string message; public: MyException(const string& msg) : message(msg) {} virtual const char* what() const noexcept override { return message.c_str(); } }; void processPayment(double amount) { if (amount <= 0) { throw MyException(“支付金额必须大于0”); } // 处理支付… } int main() { try { processPayment(-50.0); } catch (const MyException& e) { cerr << “自定义异常: “ << e.what() << endl; } return 0; }

创建自定义异常的好处:

  • 可以包含特定于应用的错误信息
  • 可以添加额外的错误处理逻辑
  • 使错误分类更清晰
  • 便于在大型项目中管理错误

🏆 异常处理最佳实践

✅ 应该这样做

  • 在构造函数和析构函数中谨慎使用异常
  • 按引用捕获异常(catch (const exception& e))
  • 从std::exception派生自定义异常
  • 在catch块中清理资源或使用RAII
  • 为可能失败的操作提供异常安全保证
  • 在文档中记录函数可能抛出的异常

❌ 避免这样做

  • 不要用异常处理正常的控制流程
  • 避免抛出原始指针或容易导致内存泄漏的类型
  • 不要在析构函数中抛出异常
  • 不要忽略捕获的异常(空的catch块)
  • 避免过度使用异常影响性能
  • 不要用异常替代参数检查
RAII原则: 资源获取即初始化(Resource Acquisition Is Initialization)是C++管理资源的关键原则。通过对象生命周期管理资源,确保即使发生异常,资源也能被正确释放。

⚠️ 常见错误

1. 未捕获的异常

如果抛出的异常没有被任何catch块捕获,程序会调用std::terminate()终止。

2. 异常屏蔽

当在catch块中抛出新的异常时,原始异常会被丢弃:

try { throw “原始错误”; } catch (…) { throw “新错误”; // 原始异常丢失 }

3. 切片问题

按值捕获派生类异常会导致对象切片:

class MyException : public exception { /*…*/ }; try { throw MyException(); } catch (exception e) { // 按值捕获 – 错误! // 这里会发生对象切片,丢失派生类信息 } // 正确做法:catch (const MyException& e)

🎓 总结

异常处理是C++中强大的错误处理机制,核心要点总结:

  • try-catch-throw 是异常处理的基础结构
  • 优先使用标准异常,必要时创建自定义异常
  • 按引用捕获异常,避免对象切片
  • 遵循RAII原则管理资源
  • 为代码提供异常安全保证
  • 避免在析构函数中抛出异常
  • 不要滥用异常 – 它们用于处理异常情况,而非控制流程

合理使用异常处理可以使你的程序:

  • 更健壮 – 能够从错误中恢复或优雅退出
  • 更清晰 – 分离正常逻辑和错误处理
  • 更安全 – 避免资源泄漏和未定义行为

“好的异常处理不是避免错误,而是优雅地处理错误。”