C++异常处理详解
1. 异常处理基础
异常是程序运行时发生的非正常情况,如除零错误、内存访问越界等。C++通过try、catch和throw机制实现异常处理:
try { // 可能抛出异常的代码 if (denominator == 0) { throw std::runtime_error("Division by zero"); } result = numerator / denominator; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; }2. 异常类型体系
C++标准库提供异常类层次结构:
- 基类:
std::exception - 派生类:
std::runtime_error(运行时错误)std::logic_error(逻辑错误)std::bad_alloc(内存分配失败)std::out_of_range(越界访问)
class CustomError : public std::runtime_error { public: CustomError(const std::string& msg) : std::runtime_error("Custom: " + msg) {} }; // 使用示例 throw CustomError("Invalid parameter");l
3. 异常安全保证
分为三个等级:
- 基本保证:资源不泄漏,对象处于有效状态
- 强保证:操作完全成功或回滚到原始状态
- 不抛保证:操作绝不抛出异常(
noexcept)
// 强保证示例 void safe_swap(T& a, T& b) noexcept { T temp(std::move(a)); a = std::move(b); b = std::move(temp); }4. RAII资源管理
资源获取即初始化(RAII)是异常安全的核心:
class FileHandler { public: FileHandler(const std::string& path) : file_(fopen(path.c_str(), "r")) { if (!file_) throw std::ios_base::failure("File open failed"); } ~FileHandler() { if (file_) fclose(file_); } // 禁用拷贝 FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; private: FILE* file_; }; // 使用示例 try { FileHandler fh("data.bin"); // 自动资源释放 } catch (...) { /* 处理异常 */ }5. 异常规范(C++11后)
noexcept声明不抛出异常noexcept(expr)条件性声明- 违反
noexcept会导致std::terminate调用
void critical_function() noexcept { // 此函数保证不抛出异常 }6. 异常处理最佳实践
- 避免过度使用:仅处理非预期错误
- 按引用捕获:
catch (const ExceptionType& e) - 避免异常屏蔽:析构函数默认设为
noexcept - 使用标准异常类型:保持异常层次清晰
- 异常中立:允许异常在调用链中传播
// 异常传播示例 void inner() { throw std::logic_error("Error"); } void outer() { try { inner(); } catch (...) { std::throw_with_nested(std::runtime_error("Outer failed")); } } int main() { try { outer(); } catch (const std::exception& e) { std::cerr << "Caught: " << e.what() << std::endl; } }7. 异常与性能
- 正常执行路径无性能开销
- 抛出异常时开销较大(约10-100倍函数调用)
- 适用场景:
- 错误处理路径不频繁
- 错误需要跨多层调用栈处理
- 替代错误码使代码更清晰
性能对比(纳秒/操作):
操作类型 正常流程 异常流程 返回错误码 5ns - 抛出异常 3ns 300ns
8. 常见陷阱
- 切片问题:
try { throw DerivedError(); } catch (BaseError e) { /* 发生对象切片 */ } // 错误 catch (const BaseError& e) { /* 正确 */ } // 正确- 异常丢失:
~Resource() noexcept { // 默认noexcept cleanup(); // 若此处抛出异常,程序终止 }- 双重抛出:
catch (...) { throw; // 正确:重新抛出原异常 // throw new_exception; // 错误:覆盖原异常 }9. C++17改进
std::terminate_handler自定义终止行为std::uncaught_exceptions()检测未处理异常数量- 嵌套异常支持:
try { /* ... */ } catch (...) { std::throw_with_nested( std::runtime_error("Additional context") ); }10. 与错误码对比
| 特性 | 异常 | 错误码 |
|---|---|---|
| 跨函数传播 | 自动 | 手动检查返回值 |
| 错误信息 | 丰富(类型+消息) | 有限(通常为整数) |
| 性能开销 | 抛出时高,正常路径无 | 每次调用都需检查 |
| 适用场景 | 不可恢复错误 | 频繁发生的可恢复错误 |
// 错误码示例 std::error_code ec; auto result = risky_operation(ec); if (ec) { /* 处理错误 */ }结论
C++异常处理提供强大的错误管理机制,但需遵循最佳实践:
- 将异常用于非预期错误
- 结合RAII保证资源安全
- 优先使用标准异常类型
- 注意
noexcept规范的使用 - 避免在析构函数和内存分配失败时抛出异常
正确使用异常可使代码更健壮,同时保持主逻辑的清晰性,是构建可靠C++系统的关键组件。