C++ 错误处理:try、catch 与现代方法

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C++ 错误处理:从 try/catch 到 RAII 和 noexcept



程序的可靠性直接取决于它对意外情况的反应方式。在 C++ 中,异常是实现这一目标的主要机制。在本文中,我们将分析经典的 try/catch/throw 结构,并探讨替代和现代方法:RAII、noexcept 以及无异常的错误处理。



1. 基本机制:try, catch, throw



异常是一个在发生错误时被“抛出”(throw)的对象。程序可以捕获它(catch)并执行纠正操作,而无需完全中断工作。



语法

#include <iostream>#include <stdexcept>

double divide(double a, double b) { if (b == 0.0) { throw std::runtime_error("除以零!"); } return a / b;}

int main() { try { double result = divide(10.0, 0.0); std::cout << "结果: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0;}


工作规则

  • throw — 创建对象的副本并将控制权传递给处理程序。
  • try — 可能发生异常的代码块。
  • catch — 按类型捕获异常。可以指定多个 catch 块。
  • 如果异常未被捕获,则调用 std::terminate()。


按基类捕获

所有标准异常都继承自 std::exception。因此,可以编写一个通用处理程序:

try {    // 代码} catch (const std::exception& e) {    std::cerr << "标准错误: " << e.what() << std::endl;} catch (...) {    std::cerr << "未知异常" << std::endl;}

catch (...) 块捕获所有异常,但无法访问异常对象。



2. 构造函数和析构函数中的异常



构造函数

如果构造函数抛出异常,则认为对象未创建,并且不会调用其析构函数。这对于资源管理很重要。示例:

class Resource {public:    Resource() {        std::cout << "资源已分配" << std::endl;        throw std::runtime_error("初始化错误");    }    ~Resource() {        std::cout << "资源已释放" << std::endl;    }};

int main() { try { Resource r; } catch (...) { std::cout << "异常已捕获" << std::endl; } // Resource 的析构函数将不会被调用! return 0;}


析构函数

从析构函数抛出异常是不好的做法。如果在栈展开期间调用析构函数,并且它抛出一个新异常,程序将异常终止。始终将析构函数编写为 noexcept(默认情况下它们就是如此)。



3. RAII — 自动资源管理

RAII(资源获取即初始化)惯用法是安全代码的关键。资源(内存、文件、互斥锁)在构造函数中获取,并在析构函数中释放。如果发生异常,所有已创建对象的析构函数会自动调用(栈展开),并且资源被释放。



使用智能指针 std::unique_ptr 的示例:

#include <memory>#include <iostream>

void process() { auto ptr = std::make_unique<int>(42); // 如果这里发生异常, // unique_ptr 将自动释放内存 throw std::runtime_error("proc 中的错误");}

博客

书籍推荐