编程C++ 系统程序员

请谈谈 C++ 中异常(exceptions)工作的机制。如何正确处理错误,以及 noexcept 的用处是什么?

用 Hintsage AI 助手通过面试

回答。

问题背景:

为了确保更可靠和结构化的错误处理,C++ 引入了异常处理机制,而不是使用返回代码。随着时间的推移,语法得到了扩展,出现了 noexcept 类型的说明符,用于控制函数抛出异常的取消。

问题:

不正确地处理异常常会导致内存泄漏、未定义行为或应用程序崩溃。如果不考虑从构造函数、析构函数或资源操作中抛出异常,则程序状态会出现严重问题。

解决方案:

基本语法是使用 try-catch。异常可以是任何类型,但建议自 std::exception 继承用户自定义异常。关键字 noexcept(C++11+)指示函数不应抛出异常;从 noexcept 函数抛出的异常会导致 std::terminate()。

代码示例:

#include <iostream> #include <stdexcept> void func() noexcept(false) { throw std::runtime_error("Error!"); } int main() { try { func(); } catch(const std::exception& ex) { std::cout << ex.what(); } }

关键特性:

  • 异常提供自动解压(unwind)栈
  • noexcept 允许编译器优化,如果函数保证不抛出
  • 只有 std::exception 及其派生类保证存在 what() 方法

被问到的陷阱问题。

在析构函数中抛出异常是可以的吗?

不可以,在栈展开期间从析构函数抛出异常会导致 std::terminate()。在析构函数中需要捕获异常。

如果从 noexcept 函数抛出异常,会发生什么?

将调用 std::terminate,程序将异常终止。noexcept 是严格契约。

可以按值捕获异常吗?这有什么危险?

可以按值捕获,但对象会被复制(object slicing)。建议使用 const &。

代码示例:

try { throw std::out_of_range("err"); } catch (const std::exception& e) { /* ... */ }

常见错误和反模式

  • 在析构函数中使用 throw
  • 捕获异常时没有记录的 catch(...)
  • 在可能的地方不指明 noexcept

生活中的例子

负面案例

代码在资源的析构函数中抛出了异常;在程序异常终止时,栈展开不正确,资源成为泄漏。

优点:

  • 可以快速发现错误

缺点:

  • 程序经常因为错误 terminate 而退出

正面案例

关键方法标记为 noexcept,析构函数在内部处理所有异常。应用了按引用捕获,所有错误都被记录。

优点:

  • 可靠性,避免泄漏
  • 行为极其可预测

缺点:

  • 开发人员在编写正确的“干净”代码时需更加严格