卡码笔记-最强八股文
首页
计算机基础
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
简历专栏
代码随想录 (opens new window)
首页
计算机基础
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
简历专栏
代码随想录 (opens new window)
  • 本栏必读

    • 关于本专栏
    • C++学习路线
    • C++面试题系优化
  • 基础与语法

  • 面向对象

  • STL 与容器

  • 内存管理

  • C++11 与现代 C++

    • C++11中的新特性有哪些
    • C++11中的多线程编程
    • 左值引用和右值引用的区别
    • 移动语义有什么作用,原理是什么
    • 完美转发的作用及实现
    • 说一下c++中stdmove与stdforward的区别
    • 说一下lambda函数
    • 仿函数与lambda性能对比
    • C++中的RAII机制
    • C++中的异常处理机制
      • 简要回答
      • 详细回答
      • 知识拓展
    • C++中的协程概念及实现
  • 智能指针

  • 并发与 I/O

# C++ 异常处理机制

面试官问:"C++ 的异常处理机制了解吗?栈展开是怎么回事?异常和错误码怎么选?"

异常处理是 C++ 错误处理的核心机制,很多人能说出 try-catch 的基本用法,但问到栈展开的具体过程、为什么必须按引用捕获、析构函数为什么不能抛异常,就答不清楚了。关键是理解异常的执行流程和它与 RAII 的配合关系。

# 简要回答

C++ 异常处理基于 try-throw-catch 三件套:

  • try 块:定义监控范围,标记"这段代码可能出错"
  • throw:抛出异常对象,立即终止当前函数执行
  • catch 块:按类型匹配捕获异常,处理错误

核心机制是栈展开(Stack Unwinding):从抛出点开始,沿调用链向上逐层退出函数,途中自动调用所有局部对象的析构函数。配合 RAII,保证即使发生异常,资源也不会泄漏。

# 详细回答

异常的执行流程

当 throw 被执行时,程序不会继续往下走,而是立即开始"栈展开"——从当前函数退出,沿着调用链一层一层往上找,直到找到一个类型匹配的 catch 块。这个过程中,每退出一层函数,该层所有局部对象的析构函数都会被自动调用。

这就是异常处理的精髓:错误处理和资源清理解耦。你不需要在每一层手动检查错误码、手动释放资源——RAII 对象的析构函数会自动搞定。

异常捕获的规则

catch 块按顺序匹配,找到第一个类型兼容的就进入。所以派生类异常的 catch 要写在基类前面,否则永远匹配不到。catch(...) 能捕获所有异常,通常放在最后做兜底。

noexcept 的作用

C++11 引入 noexcept 说明符,告诉编译器"这个函数保证不抛异常"。编译器可以据此做优化(比如 STL 容器扩容时,如果移动构造是 noexcept 的,就用移动而不是拷贝)。如果标了 noexcept 的函数实际抛了异常,程序直接 terminate,不做栈展开。

异常安全的三个级别

  • 基本保证:异常发生后,对象处于合法状态,资源不泄漏
  • 强保证:异常发生后,程序状态回滚到操作之前(要么成功,要么什么都没变)
  • 不抛保证:函数保证不抛异常(用 noexcept 标记)

C++ 异常处理机制

# 知识拓展

面试官可能追问:

Q1: 异常和错误码怎么选?

错误码适合高频、可预期的错误(比如文件不存在、网络超时),开销小但容易被忽略。异常适合低频、不可恢复的错误(比如内存分配失败、违反前置条件),不会被忽略但有性能开销。实际项目中通常混用:底层库用错误码,上层业务逻辑用异常。构造函数只能用异常报告失败,这是没得选的。

Q2: 为什么必须按引用捕获异常?

按值捕获会发生对象切片——如果抛出的是派生类异常,按基类值捕获会丢失派生类信息,多态失效。按引用捕获保持多态性,而且避免了不必要的拷贝。用 const 引用更好,因为你通常不需要修改异常对象。

Q3: 析构函数为什么不能抛异常?

因为栈展开过程中会调用局部对象的析构函数。如果这时候析构函数又抛了异常,就出现了"异常嵌套"——C++ 标准规定这种情况直接调用 terminate 终止程序。所以析构函数必须标 noexcept,内部的异常必须自己吞掉(try-catch 包住,记录日志但不往外抛)。

Q4: 异常处理有什么性能开销?

现代编译器用"零开销异常"(zero-cost exception)实现:正常执行路径没有额外开销,只有真正抛出异常时才付出代价(栈展开、查找 catch 块)。所以异常适合处理"异常情况"——如果某个错误发生的概率很高(比如每次循环都可能触发),用错误码更合适;如果是真正的异常情况(比如内存耗尽),用异常没问题。

Last Updated: 5/23/2026, 4:51:07 PM

← C++中的RAII机制 C++中的协程概念及实现 →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪