17370845950

C++异常处理怎么写 C++ try-catch-throw错误捕获机制【稳健】
C++异常处理应抛出std::exception派生对象,catch按派生到基类顺序书写,禁用try-catch控制正常流程,析构函数不得throw,业务层优先用std::expected而非异常。

throw 抛出异常时必须是对象或指针,不能是原始类型

很多初学者会写 throw 42throw "error",这在语法上合法但极不稳健。C++ 标准推荐抛出继承自 std::exception 的对象(如 std::runtime_error),否则 catch 块难以统一处理,且无法调用 .what() 获取描述。

  • ✅ 推荐:throw std::runtime_error("file not found")
  • ❌ 避免:throw "file not found"(C 字符串字面量,生命周期短,catch 中可能悬垂)
  • ⚠️ 注意:throw 42 虽能被 catch(int) 捕获,但丢失上下文、不可扩展、无法携带堆栈信息

catch 块顺序必须从派生类到基类,否则会被截断

C++ 的 catch 匹配是静态、顺序匹配的。如果把 catch(const std::exception&) 写在前面,后面所有派生类(如 std::logic_error)都将被它“吃掉”,导致更具体的错误处理逻辑永远不执行。

  • ✅ 正确顺序:catch(const std::invalid_argument&)catch(const std::runtime_error&)catch(const std::exception&)
  • ❌ 错误顺序:catch(const std::exception&) 放最前 → 所有子类异常都进不来
  • ⚠️ 补充:不要写 catch(...) 除非你真要兜底日志+终止,它捕获一切(包括信号、硬件异常),且无类型信息,极易掩盖问题

try-catch 不该用来控制正常流程,尤其别替代 if-else 或返回码

异常机制设计初衷是处理「罕见、意外、无法局部恢复」的错误,不是替代条件分支。滥用会导致性能骤降(即使没抛出,某些编译器仍需维护栈展开表)、代码可读性崩坏、静态分析工具失效。

  • ❌ 反模式:try { parse_int(s); } catch(...) { return false; } —— 应该用 std::

    from_chars
    或预检查
  • ✅ 合理场景:打开文件失败、网络连接超时、内存分配失败(new(std::nothrow) 不够用时)
  • ⚠️ 关键点:函数接口契约要明确——若文档说“抛 std::out_of_range”,调用方就必须处理;若只靠异常传递业务状态(如“用户不存在”),就混淆了错误与业务逻辑

析构函数里禁止 throw,否则程序直接 terminate

C++ 标准规定:若在栈展开过程中(即另一个异常正被处理时)析构函数又抛异常,std::terminate() 立即调用,进程退出。而析构函数常被隐式调用(如作用域结束、异常传播中自动析构局部对象),风险极高。

  • ✅ 安全做法:析构函数内所有可能失败的操作必须用 nothrow 方式处理,或用 try/catch 吞掉异常(并记录日志)
  • ❌ 危险写法:~MyResource() { close(fd); if (errno) throw std::system_error(errno, ...); }
  • ⚠️ 提醒:用 noexcept 显式标注析构函数(如 ~MyClass() noexcept)不仅是文档说明,更是编译器优化和容器安全的前提
异常机制本身不复杂,真正难的是判断「什么算异常」。越靠近系统层(IO、内存、并发),越容易找到明确的异常边界;越往业务层走,“错误”的定义就越模糊——这时候,老老实实返回 std::expectedstd::variant,往往比硬套 try-catch 更稳健。