C++ 异常处理
C++ 异常处理
Section titled “C++ 异常处理”一个 异常(exception) 是指在程序执行期间发生的、中断正常指令流的意外事件或错误情况。例子包括尝试除以零、内存分配失败或遇到无效输入。
C++ 提供了一种结构化的机制,使用三个关键字来处理这些异常情况:try、catch 和 throw。
try:一个try块(try block) 包围了一段可能发生异常的代码。它表示您已准备好处理在此块内可能抛出的异常。throw:当检测到错误情况时,使用throw关键字后跟一个表达式来 抛出(throw) 异常。表达式的类型决定了抛出的异常类型。catch:一个catch块(catch block)紧跟在一个try块(或其他catch块)之后。它充当异常处理器。它指定可以捕获的异常类型,并包含处理该异常的代码。
基本语法如下所示:
try { // 可能抛出异常的代码(受保护的代码) // ...} catch (const ExceptionType1& e1) { // 按常量引用捕获特定异常类型 // 处理 ExceptionType1 // 通过 e1 访问异常详情(例如,对于派生自 std::exception 的类型,使用 e1.what())} catch (const ExceptionType2& e2) { // 处理 ExceptionType2 // ...} catch (...) { // 捕获所有类型的处理器(谨慎使用) // 处理任何其他意外的异常类型}try 块内的代码正常执行。如果在 try 块内(或从 try 块调用的函数内)抛出异常,程序会立即跳转以寻找匹配的 catch 块。第一个类型与抛出的异常匹配(或为其基类)的 catch 块将被执行。如果找不到匹配的 catch 块,程序将终止(通常通过调用 std::terminate)。
您可以使用 throw 语句从任何地方抛出异常。操作数可以是任何类型,但强烈建议抛出派生自 std::exception 的类对象。
示例:抛出除以零的异常。
#include <stdexcept> // 标准异常类(如 std::runtime_error)所需#include <string>
double safe_division(int a, int b) { if (b == 0) { // 抛出一个标准异常类型的对象 throw std::runtime_error("Division by zero condition!"); } // 注意:整数除法会截断。为进行浮点除法而进行类型转换。 return static_cast<double>(a) / b;}catch 块指定了它可以处理的异常类型。最佳实践是按 常量引用 (const ExceptionType& e) 捕获异常。这可以避免不必要的拷贝,并在捕获多态异常类型时防止对象切片(slicing)。
try { // ... 可能调用 safe_division 的代码 ... double result = safe_division(10, 0); // ... 如果抛出异常,这部分代码不会被执行 ...} catch (const std::runtime_error& e) { // 处理特定的运行时错误 std::cerr << "Error caught: " << e.what() << std::endl; // e.what() 返回构造时传入的消息("Division by zero condition!")}您可以使用多个 catch 块来处理不同的异常类型。它们按顺序检查。一个捕获所有类型的处理器 catch (...) 会捕获任何类型的异常,但它不提供关于异常的任何信息。请谨慎使用它,通常用于在重新抛出之前进行清理。
演示 try/catch 的示例:
#include <iostream>#include <stdexcept>#include <string>
// 假设上面定义的 safe_division 函数在这里double safe_division(int a, int b) { if (b == 0) { throw std::runtime_error("Division by zero condition!"); } return static_cast<double>(a) / b;}
int main() { int x = 50; int y = 0; double z = 0;
try { std::cout << "Attempting division..." << std::endl; z = safe_division(x, y); std::cout << "Result: " << z << std::endl; // This line won't execute } catch (const std::runtime_error& e) { // 按常量引用捕获特定的异常类型 std::cerr << "Caught a runtime error: " << e.what() << std::endl; } catch (const std::exception& e) { // 捕获任何其他标准异常(如果 safe_division 抛出了不同的异常) std::cerr << "Caught a standard exception: " << e.what() << std::endl; } catch (...) { std::cerr << "Caught an unknown exception type." << std::endl; }
std::cout << "Program continues after try-catch block." << std::endl;
return 0;}编译并运行后,将产生以下输出:
Attempting division...Caught a runtime error: Division by zero condition!Program continues after try-catch block.C++ 标准异常
Section titled “C++ 标准异常”C++ 标准库在 <stdexcept> 头文件(以及 <exception> 中的基类 std::exception)中定义了标准异常类层次结构。建议抛出这些标准类型(或从它们派生的类型)。
所有标准异常的基类是 std::exception。主要的派生类包括:
std::logic_error:表示程序内部逻辑错误(例如,前置条件被违反等)。通常在运行时之前理论上可以检测到。std::domain_error:数学函数的域错误。std::invalid_argument:传递给函数的无效参数。std::length_error:尝试创建大于支持的最大尺寸的对象。std::out_of_range:参数值不在预期范围内(例如,通过.at()访问超出范围的 vector 元素)。std::runtime_error:表示可能只能在运行时检测到的错误。std::overflow_error:发生算术溢出。std::underflow_error:发生算术下溢。std::range_error:计算结果无法表示。std::system_error(C++11):表示低级别操作系统错误。std::bad_alloc:当内存分配失败时由new抛出。std::bad_cast:当引用转换失败时由dynamic_cast抛出。std::bad_typeid:对空指针使用typeid时抛出。std::bad_exception:用于非预期的异常处理机制。
所有派生自 std::exception 的类都有一个虚成员函数 what(),它返回一个 C 风格字符串(const char*)来描述异常。
定义自定义异常
Section titled “定义自定义异常”您可以为特定于应用程序的错误定义自己的异常类。最佳实践是将自定义异常类派生自 std::exception 或其标准派生类之一(如 std::runtime_error 或 std::logic_error)。
#include <iostream>#include <exception>#include <string>
// 定义一个派生自 std::runtime_error 的自定义异常class MyCustomException : public std::runtime_error {public: // 构造函数接受一个消息字符串 explicit MyCustomException(const std::string& message) : std::runtime_error(message) {} // 将消息传递给基类构造函数
// 可选:如果需要,添加自定义成员或方法 // const char* what() const noexcept override; // 已由 std::runtime_error 提供};
int main() { try { // 模拟错误情况 throw MyCustomException("Something specific went wrong in my application."); } catch (const MyCustomException& e) { std::cerr << "Caught MyCustomException: " << e.what() << std::endl; } catch (const std::exception& e) { // 这将捕获可能发生的其他标准异常 std::cerr << "Caught standard exception: " << e.what() << std::endl; } return 0;}这将产生以下输出:
Caught MyCustomException: Something specific went wrong in my application.异常安全与 RAII
Section titled “异常安全与 RAII”编写异常安全(exception-safe)的代码至关重要。这意味着即使抛出异常,也要确保程序保持在有效状态。实现这一目标的主要 C++ 技术是 RAII(Resource Acquisition Is Initialization,资源获取即初始化)。RAII 利用对象的生命周期(由构造函数和析构函数管理)来自动管理资源,例如内存、文件句柄、锁等。当抛出异常时,栈上对象的析构函数会自动调用,确保正确清理。智能指针(std::unique_ptr、std::shared_ptr)和标准库容器都大量依赖于 RAII。
noexcept 说明符
Section titled “noexcept 说明符”您可以使用 noexcept 声明函数,指定它不会抛出任何异常。这可以带来编译器优化。如果一个 noexcept 函数确实抛出了异常,则会调用 std::terminate。析构函数通常应该是 noexcept 的。
void function_that_never_throws() noexcept { /* ... */ }