C++异常处理 进阶
在掌握了try、throw、catch的基础用法后想要写出健壮、安全、可维护的 C 代码就必须深入学习异常处理的进阶知识。本文将聚焦标准异常体系、自定义异常、noexcept、异常安全、构造 / 析构异常等核心进阶内容帮你从 “会用异常” 提升到 “用好异常”。一、C 标准异常类体系C 内置了一套完整的标准异常类所有标准异常都继承自同一个基类std::exception定义在exception头文件中。使用标准异常的好处统一、规范、能被所有 C 开发者理解也能和标准库兼容。1. 标准异常继承结构std::exception基类 ├─ std::logic_error逻辑错误代码bug导致可提前避免 │ ├─ std::invalid_argument 无效参数 │ ├─ std::out_of_range 越界访问 │ └─ std::domain_error 定义域错误 └─ std::runtime_error运行时错误运行时才出现无法提前预知 ├─ std::bad_alloc 内存分配失败new失败 ├─ std::bad_cast 类型转换失败 └─ std::runtime_error 通用运行时错误2. 关键成员函数所有标准异常都包含一个虚函数virtual const char* what() const noexcept;作用返回异常的描述信息。3. 使用示例#include iostream #include exception // 标准异常 #include stdexcept // logic_error / runtime_error using namespace std; void test() { // 抛出 越界异常 throw out_of_range(数组下标超出有效范围); } int main() { try { test(); } // 捕获基类 exception可捕获所有标准异常 catch (const exception e) { // 输出错误信息 cout 捕获异常 e.what() endl; } return 0; }建议实际开发中优先使用标准异常不要随意抛字符串 / 数字。二、自定义异常类进阶必备标准异常不够用时可以继承标准异常创建自己的异常类携带更多错误信息错误码、错误位置、自定义描述等。自定义异常示例#include iostream #include exception #include string using namespace std; // 自定义异常继承 runtime_error class MyException : public runtime_error { private: int errorCode; // 自定义错误码 public: // 构造函数 MyException(const string msg, int code) : runtime_error(msg), errorCode(code) {} // 获取错误码 int getCode() const { return errorCode; } }; // 测试函数 void login() { throw MyException(用户名不存在, 1001); } int main() { try { login(); } catch (const MyException e) { cout 异常信息 e.what() endl; cout 错误码 e.getCode() endl; } return 0; }优点可携带自定义数据可被精准捕获兼容标准异常体系三、noexcept 关键字C11noexcept用来声明函数不会抛出异常。1. 作用告诉编译器此函数无异常编译器可优化告诉调用者不用处理异常如果noexcept函数内部抛出异常且未捕获程序会直接终止std::terminate2. 用法// 声明不会抛异常 void func() noexcept { // 代码... } // 条件性 noexcept void func2() noexcept(true); // 不抛 void func3() noexcept(false); // 可能抛3. 特别重要析构函数默认是 noexcept 的所以绝对不要在析构函数里抛异常否则程序必崩。四、异常安全Exception Safety异常安全是指即使发生异常程序也不会泄漏资源、不会破坏数据结构、不会产生不一致状态。异常安全分为 3 个等级基本保证异常发生资源不泄漏对象合法强保证要么成功要么回滚无副作用不抛保证绝对不会抛异常noexcept最常用方案RAII 智能指针利用 C 的栈展开机制局部对象会自动析构从而自动释放资源。示例避免内存泄漏void test() { int* p new int[100]; // 若这里抛异常p会泄漏 throw runtime_error(出错); delete[] p; // 永远执行不到 }安全写法void test() { unique_ptrint[] p(new int[100]); throw runtime_error(出错); // 无需手动 delete智能指针自动释放 }五、构造函数 析构函数 与 异常1. 构造函数可以抛异常构造失败如打开文件失败、内存不足应该抛异常构造函数抛异常对象不算创建成功不会调用析构函数2. 析构函数绝对不能抛异常析构函数默认noexcept一旦抛异常程序直接终止析构中如果可能出错必须内部捕获不往外抛错误示例~MyClass() { throw 崩溃; // 严重错误 }六、捕获异常的高级技巧1. 按引用捕获推荐catch (const exception e)避免对象切片slicing效率高支持多态调用what()2. 异常重新抛出catch (const exception e) { // 处理一部分 throw; // 重新抛出原异常保留类型信息 }3. 万能捕获 标准异常try { // ... } catch (const exception e) { // 处理标准异常 } catch (...) { // 处理所有其他异常 }七、异常使用最佳实践企业级抛对象捕引用优先使用标准异常再自定义异常析构函数绝不抛异常用RAII / 智能指针保证异常安全用noexcept明确函数是否抛异常不要用异常控制正常业务流程最外层用catch(...)防止程序崩溃总结C 异常处理进阶核心标准异常体系统一错误类型自定义异常携带更多错误信息noexcept标记无异常函数优化与安全异常安全RAII 避免资源泄漏构造 / 析构构造可抛析构绝不抛最佳实践抛对象、捕引用、少用裸指针掌握这些内容你就能写出稳定、安全、专业的 C 代码。总结标准异常继承std::exception用what()获取信息自定义异常应继承标准异常支持扩展错误信息noexcept表示不抛异常析构默认 noexcept异常安全依靠 RAII智能指针实现资源自动释放构造函数可抛异常析构函数绝对不能抛。