【C++】一篇文章了解C++的异常处理机制

【C++】一篇文章彻底搞懂 C++ 的异常处理机制
(2025-2026 生产级实用总结,面试 + 项目必备)

C++ 的异常处理(Exception Handling)是语言级别的错误处理机制,核心目标是:把错误从发生的地方“扔”到能处理它的地方,而不是层层返回错误码。

一、C++ 异常处理的核心四件套

关键字作用出现位置常见写法示例
throw抛出异常任何函数内部throw std::runtime_error("文件打开失败");
try监视可能抛异常的代码块函数体中try { ... }
catch捕获并处理特定类型的异常紧跟在 try 后面catch(const std::exception& e) { ... }
noexcept声明函数不会抛出异常(C++11+)函数声明/定义后void func() noexcept;

二、异常处理的基本写法模板(最常用)

#include <iostream>
#include <stdexcept>
#include <string>

void riskyOperation(int value) {
    if (value < 0) {
        throw std::invalid_argument("值不能为负数");
    }
    if (value > 100) {
        throw std::out_of_range("值超出允许范围");
    }
    std::cout << "操作成功,value = " << value << "\n";
}

int main() {
    try {
        riskyOperation(-5);
        riskyOperation(150);
        riskyOperation(50);  // 这一行不会执行
    }
    catch (const std::invalid_argument& e) {
        std::cerr << "参数错误: " << e.what() << "\n";
    }
    catch (const std::out_of_range& e) {
        std::cerr << "范围错误: " << e.what() << "\n";
    }
    catch (const std::exception& e) {           // 捕获所有标准异常
        std::cerr << "标准异常: " << e.what() << "\n";
    }
    catch (...) {                               // 捕获所有未知异常(兜底)
        std::cerr << "未知异常发生!\n";
    }

    std::cout << "程序继续执行...\n";
    return 0;
}

输出示例(只执行到第一条 throw):

参数错误: 值不能为负数
程序继续执行...

三、异常传递的三大规则(非常重要!)

  1. 栈展开(Stack Unwinding)
    当异常抛出后,C++ 会沿着调用栈逆向查找最近的 try-catch
    在栈展开过程中,所有局部对象都会调用析构函数(RAII 保证资源释放)。
  2. 异常只能被捕获一次
    找到第一个匹配的 catch 后,后面的 catch 不会执行。
  3. catch 的匹配顺序(从上到下)
  • 精确匹配 > 基类匹配 > …
  • 所以要把派生类异常放前面基类放后面。 错误写法(永远捕获不到 derived):
   catch (std::exception& e) { ... }      // 先捕获基类
   catch (std::runtime_error& e) { ... }  // 永远执行不到

正确写法:

   catch (std::runtime_error& e) { ... }
   catch (std::exception& e) { ... }

四、C++ 标准异常体系(面试常考)

std::exception                    (所有标准异常的基类)
├── logic_error                   (逻辑错误,通常是编程错误)
│   ├── invalid_argument
│   ├── out_of_range
│   ├── domain_error
│   └── ...
├── runtime_error                 (运行时错误,通常是环境问题)
│   ├── overflow_error
│   ├── underflow_error
│   ├── range_error
│   └── ...
└── bad_alloc                     (new 内存分配失败)
    bad_cast
    bad_typeid
    ...

建议:自己定义的异常类最好继承 std::exceptionstd::runtime_error

class MyBusinessException : public std::runtime_error {
public:
    explicit MyBusinessException(const std::string& msg)
        : std::runtime_error(msg) {}
};

五、noexcept 的使用(C++11 后非常重要)

void safe_func() noexcept;               // 承诺不抛异常,如果抛了 → std::terminate()

void maybe_throw() noexcept(false);      // 显式允许抛异常(默认就是)

// 条件 noexcept(最常用在模板中)
template<typename T>
void process(T&& t) noexcept(std::is_nothrow_move_constructible_v<T>) {
    // ...
}

noexcept 的意义(性能 & 安全性):

  • 允许编译器做更多优化(不生成异常展开代码)
  • 移动语义中大量使用(std::vector 扩容时优先调用 noexcept move)
  • 如果函数声明 noexcept 但真的抛了 → 直接调用 std::terminate()(程序崩溃)

六、生产环境中异常处理的常见最佳实践(2025-2026)

场景推荐做法为什么
资源管理全部用 RAII(智能指针、lock_guard 等)栈展开时自动释放资源
构造函数抛异常允许抛,在对象未构造成功时抛出防止半成品对象
析构函数绝对禁止抛异常(C++ 标准强制)栈展开时抛异常 → std::terminate()
性能敏感代码用 noexcept + 错误码代替异常异常展开有开销(现代编译器已优化很多)
库/框架设计提供异常版本 + noexcept 版本双接口让调用者选择(像 std::vector::at() vs [])
捕获范围尽量 catch 具体类型,少用 catch(…)避免吞掉未知异常,方便调试
日志catch 后记录异常信息 + 栈追踪(如果有)生产环境必须可追溯

七、异常 vs 错误码 vs std::expected(C++23+)

方式优点缺点推荐场景
返回错误码性能最高、可预测容易忽略、代码丑陋性能极致场景(游戏引擎底层)
抛异常清晰、集中处理、栈展开安全有微小性能开销、难以预测路径业务逻辑层、库设计
std::expected(C++23)无开销、可预测、现代语法稍啰嗦、普及度刚起步新项目、追求现代风格

一句话总结
异常适合“异常情况”(程序员无法预料的错误),错误码/expected 适合“正常业务错误”

八、经典面试题速记

  1. 析构函数为什么不能抛异常?
  2. noexcept 和 throw() 的区别?
  3. 异常抛出后,栈上对象析构顺序是什么?
  4. catch(…) 会捕获什么?有什么风险?
  5. 如何在不使用异常的情况下实现 RAII?
  6. std::uncaught_exceptions() 有什么用?

你现在是用异常写业务代码,还是在做底层/性能敏感模块?
或者遇到了具体异常处理的痛点(比如第三方库乱抛、栈溢出、terminate 被调用)?
告诉我,我可以给你更针对性的代码示例或解决方案。

文章已创建 4791

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部