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

throw 抛出异常时必须是对象或指针,不能是原始类型
很多初学者会写 throw 42 或 throw "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() 立即调用,进程退出。而析构函数常被隐式调用(如作用域结束、异常传播中自动析构局部对象),风险极高。
立即学习“C++免费学习笔记(深入)”;
- ✅ 安全做法:析构函数内所有可能失败的操作必须用 nothrow 方式处理,或用
try/catch吞掉异常(并记录日志) - ❌ 危险写法:
~MyResource() { close(fd); if (errno) throw std::system_error(errno, ...); } - ⚠️ 提醒:用
noexcept显式标注析构函数(如~MyClass() noexcept)不仅是文档说明,更是编译器优化和容器安全的前提
std::expected 或 std::variant,往往比硬套 try-catch 更稳健。









