C++ 异常机制详解:从入门到工程实践
前言在 C 语言中我们处理错误的方式非常有限终止程序如assert断言粗暴直接用户体验极差生产环境不可接受返回错误码最常用的方式但需要程序员手动逐层判断、查询错误码繁琐且容易遗漏setjmp/longjmp非局部跳转使用复杂极少使用C 语言主流方案是返回错误码仅极端严重错误才终止程序。 而C 引入了异常处理机制让错误处理更优雅、更清晰彻底解决了 C 语言错误处理的痛点。一、C 异常的核心概念异常当函数发生错误时主动抛出异常让调用者直接捕获并处理无需逐层传递错误码。三个核心关键字throw抛出异常触发错误处理try包裹可能抛出异常的代码标记为保护代码catch捕获并处理异常支持多个 catch 匹配不同类型基础语法try { // 可能抛出异常的代码 } catch (异常类型1 e) { // 处理类型1的异常 } catch (异常类型2 e) { // 处理类型2的异常 }二、异常的使用规则1. 异常抛出与捕获原则按类型匹配异常对象的类型决定激活哪个 catch就近匹配调用链中离 throw 最近且类型匹配的 catch 会被执行拷贝生成抛出异常时会生成临时拷贝对象catch 执行完后销毁catch(...)捕获任意类型异常兜底方案无法获取具体错误信息父子类匹配抛出派生类对象可以用基类捕获工程中最实用2. 异常栈展开机制函数调用链main() → func3() → func2() → func1()若func1抛出异常检查 throw 是否在 try 内无匹配 catch →退出当前函数栈向上层查找直到 main 函数仍无匹配 →程序直接终止找到匹配 catch → 处理完成后继续执行 catch 后续代码✅工程规范必须在最外层加catch(...)兜底防止程序崩溃代码示例基础异常捕获#include iostream using namespace std; // 除法函数除0时抛出异常 double Division(int a, int b) { if (b 0) throw Division by zero condition!; // 抛出字符串类型异常 return (double)a / b; } void Func() { int len, time; cin len time; cout Division(len, time) endl; } int main() { try { Func(); } catch (const char* errmsg) { // 匹配字符串异常 cout 错误信息 errmsg endl; } catch (...) { // 兜底捕获 cout 未知异常 endl; } return 0; }3. 异常的重新抛出场景当前 catch无法完全处理异常仅做资源清理再抛给上层处理。#include iostream using namespace std; double Division(int a, int b) { if (b 0) throw 除0错误; return (double)a / b; } void Func() { int* array new int[10]; // 动态内存 try { int a, b; cin a b; cout Division(a, b) endl; } catch (...) { // 先释放资源避免内存泄漏 delete[] array; throw; // 重新抛出原异常 } delete[] array; } int main() { try { Func(); } catch (const char* err) { cout 最终处理 err endl; } return 0; }4. 异常安全重点异常会打断正常执行流极易引发资源泄漏必须遵守规则构造函数不要抛异常会导致对象不完整、未初始化析构函数不要抛异常会导致资源无法释放内存泄漏、句柄未关闭解决方案使用RAII 机制智能指针、容器等自动管理资源5. 异常规范C98用于声明函数是否抛异常、抛什么类型提高代码可读性// 只能抛出 A/B/C/D 类型异常 void fun() throw(A, B, C, D); // 不抛任何异常 void func() throw(); // 标准new只抛 bad_alloc void* operator new(size_t size) throw(std::bad_alloc);三、工程最佳实践自定义异常体系项目中禁止随意抛异常必须统一继承体系外层只需捕获基类即可。#include iostream #include string using namespace std; // 自定义异常基类 class Exception { protected: string _errmsg; // 错误信息 int _id; // 错误码 public: Exception(const string msg, int id) :_errmsg(msg), _id(id) {} virtual string what() const { return _errmsg; } }; // 数据库异常 class SqlException : public Exception { public: SqlException(const string msg, int id) :Exception(msg, id) {} }; // 缓存异常 class CacheException : public Exception { public: CacheException(const string msg, int id) :Exception(msg, id) {} }; int main() { try { // 抛出派生类对象 throw SqlException(数据库连接失败, 1001); } catch (const Exception e) { // 捕获基类统一处理 cout 异常信息 e.what() endl; } catch (...) { cout 未知异常 endl; } return 0; }四、C 标准库异常体系C 标准库提供了通用异常定义在exception根类std::exception常用子类bad_allocnew 内存分配失败out_of_range越界访问vector::atinvalid_argument无效参数runtime_error运行时错误代码示例#include iostream #include vector #include exception using namespace std; int main() { try { vectorint v(10); v.at(100) 10; // 越界抛 out_of_range } catch (const exception e) { cout 标准异常 e.what() endl; } return 0; }五、C 异常的优缺点优点信息更丰富可携带错误详情、堆栈方便定位 Bug无需逐层传递深层错误直接跳到 catch代码更简洁第三方库通用boost、gtest 等主流库都使用异常无返回值函数友好构造函数、运算符重载无法用错误码面向对象标准Java、C#、Python 都用异常大势所趋缺点执行流跳转混乱调试、追踪难度增加轻微性能开销现代硬件可忽略易引发资源泄漏必须掌握 RAII学习成本高标准库异常不好用企业大多自定义异常体系滥用会灾难必须规范抛出、统一继承总结C 异常 try catch throw是现代化错误处理方案核心机制栈展开 类型匹配 父子类捕获工程规范自定义异常基类统一管理外层必须catch(...)兜底构造 / 析构不抛异常用 RAII 管理资源