C++标准异常类:stdexcept深度解析

C++标准异常类:stdexcept深度解析

编码文章call10242025-08-03 1:01:594A+A-

C++标准库通过 <stdexcept> 头文件提供了一系列标准的异常类。这些类都派生自基类 std::exception (定义在 <exception> 头文件中),并为常见的程序错误情况提供了具体的异常类型。使用这些标准异常类可以使错误处理更加规范和易于理解。

std::exception基类

在深入 <stdexcept> 之前,回顾一下 std::exception

 namespace std {
   class exception {
   public:
     exception() noexcept;
     exception(const exception&) noexcept;
     exception& operator=(const exception&) noexcept;
     virtual ~exception() noexcept;
     virtual const char* what() const noexcept;
   };
 }
  • what(): 这是一个虚函数,返回一个C风格字符串(const char*),描述异常的具体信息。派生类通常会重写此函数以提供更具体的错误消息。

<stdexcept>中的异常类层次结构

<stdexcept> 中定义的异常类形成了一个层次结构,它们都直接或间接地继承自 std::exception

主要的两个分支是:

  1. std::logic_error: 表示程序逻辑中的错误。这类错误理论上可以在程序运行前通过代码检查发现。
  2. std::runtime_error: 表示只有在程序运行时才能检测到的错误。

1. std::logic_error及其派生类

std::logic_error 本身可以被抛出,或者使用其更具体的派生类。

 #include <stdexcept> // For std::logic_error and its derivatives
 #include <string>
 
 // Constructor for std::logic_error and its children typically takes a const char* or std::string
 // std::logic_error(const std::string& what_arg);
 // std::logic_error(const char* what_arg);

其主要派生类包括:

  • std::domain_error: 当传递给函数的参数值不在函数预期的有效域内时抛出。
    • 例如:计算一个负数的平方根,如果数学库设计为只接受非负数,则可能抛出此异常。
  • #include <cmath> // For std::sqrt
    #include <stdexcept>
    #include <iostream>

    double calculate_sqrt(double val) {
    if (val < 0) {
    throw std::domain_error("Argument to sqrt must be non-negative.");
    }
    return std::sqrt(val);
    }
  • std::invalid_argument: 当传递给函数的参数无效时抛出。这通常比 domain_error 更通用,指参数本身不符合某种规范,但不一定是数学域的问题。
    • 例如:一个函数期望一个非空字符串,但收到了一个空字符串。
  • #include <string>
    #include <stdexcept>
    #include <iostream>

    void process_string(const std::string& s) {
    if (s.empty()) {
    throw std::invalid_argument("Input string cannot be empty.");
    }
    // ... process s ...
    }
  • std::length_error: 当尝试创建一个超出其最大允许长度的对象时抛出。
    • 例如:尝试创建一个过大的 std::stringstd::vector
  • #include <vector>
    #include <stdexcept>
    #include <iostream>

    void create_large_vector(size_t sz) {
    try {
    std::vector<int> v;
    v.reserve(sz); // May throw if sz is excessively large
    // Or, more directly:
    if (sz > v.max_size()) { // Check against max_size()
    throw std::length_error("Requested vector size exceeds maximum allowed size.");
    }
    v.resize(sz); // This might throw std::bad_alloc or std::length_error
    }
    catch (const std::length_error& e) {
    std::cerr << "Length error: " << e.what() << std::endl;
    }
    catch (const std::bad_alloc& e) {
    std::cerr << "Allocation error: " << e.what() << std::endl;
    }
    }
  • std::out_of_range: 当试图访问一个容器元素,而使用的索引或键超出了有效范围时抛出。
    • 例如:std::vector::at()std::map::at() 在索引/键不存在时会抛出此异常。
  • #include <vector>
    #include <stdexcept>
    #include <iostream>

    void access_vector_element(const std::vector<int>& vec, size_t index) {
    try {
    int val = vec.at(index); // .at() performs bounds checking
    std::cout << "Element at index " << index << " is " << val << std::endl;
    }
    catch (const std::out_of_range& e) {
    std::cerr << "Out of range error: " << e.what() << std::endl;
    }
    }

2. std::runtime_error及其派生类

std::runtime_error 本身可以被抛出,或者使用其更具体的派生类。

 #include <stdexcept> // For std::runtime_error and its derivatives
 #include <string>
 
 // Constructor for std::runtime_error and its children typically takes a const char* or std::string
 // std::runtime_error(const std::string& what_arg);
 // std::runtime_error(const char* what_arg);

其主要派生类包括:

  • std::range_error: 当内部计算的结果无法用目标类型表示(即发生范围溢出)时抛出。
    • 例如:一个数学计算产生了超出目标类型(如 double)表示范围的值。
  • #include <stdexcept>
    #include <iostream>
    #include <cmath> // For std::exp

    double safe_exp(double val) {
    double result = std::exp(val);
    if (std::isinf(result) && val > 0) { // Check if exp overflowed to infinity
    throw std::range_error("Result of exp() is too large to be represented.");
    }
    return result;
    }
  • std::overflow_error: 当算术上溢发生时抛出。这通常指计算结果大于目标类型能表示的最大值。
    • range_error 类似,但更侧重于算术运算本身的上溢。
  • #include <stdexcept>
    #include <iostream>
    #include <limits> // For std::numeric_limits

    int add_with_overflow_check(int a, int b) {
    if ((b > 0 && a > std::numeric_limits<int>::max() - b) ||
    (b < 0 && a < std::numeric_limits<int>::min() - b)) {
    throw std::overflow_error("Arithmetic overflow detected during addition.");
    }
    return a + b;
    }
  • std::underflow_error: 当算术下溢发生时抛出。这通常指计算结果(在绝对值上)小于目标类型能表示的最小正规化数,且可能导致精度损失或变为零。
  • #include <stdexcept>
    #include <iostream>

    double divide_with_underflow_check(double a, double b) {
    if (b != 0.0 && std::abs(a / b) < std::numeric_limits<double>::min() && a != 0.0) {
    // This check is simplistic; proper underflow detection can be complex
    // throw std::underflow_error("Arithmetic underflow detected during division.");
    }
    if (b == 0.0 && a != 0.0) throw std::runtime_error("Division by zero");
    if (b == 0.0 && a == 0.0) return 0.0; // Or throw, depending on convention
    return a / b;
    }
  • 注意:现代浮点数通常有非正规化数,使得下溢处理更平滑,直接抛出 std::underflow_error 的场景可能不那么常见,除非显式检查并抛出。
  • std::system_error (C++11): 这个异常类定义在 <system_error> 头文件中,但它也属于运行时错误的一种。它用于报告来自操作系统或其他底层接口的错误。它通常包含一个错误码 (std::error_code) 和一个描述性字符串。
    • 虽然不直接在 <stdexcept> 中定义,但概念上与 runtime_error 相关。

如何选择和使用标准异常

  1. 优先使用最具体的异常类型: 如果错误情况符合某个特定派生类的描述(如 std::out_of_range),则应优先使用该类型。这使得 catch 块可以更精确地捕获和处理特定类型的错误。
  2. 提供有意义的 what() 消息: 在构造异常对象时,传递一个清晰、简洁且信息丰富的字符串,说明错误的原因。这个消息对调试非常有帮助。
  3. throw std::invalid_argument("User ID '" + userId + "' not found.");
  4. 按继承层次捕获异常: 在 try-catch 块中,如果需要捕获多种相关的异常,应该将派生类异常的 catch 块放在基类异常的 catch 块之前。否则,基类的 catch 块会先捕获到派生类的异常对象。
  5. try {
    // ... code that might throw ...
    } catch (const std::out_of_range& e) {
    // Handle out_of_range specifically
    std::cerr << "Specific: Out of range: " << e.what() << std::endl;
    } catch (const std::logic_error& e) {
    // Handle other logic errors
    std::cerr << "Logic error: " << e.what() << std::endl;
    } catch (const std::runtime_error& e) {
    // Handle runtime errors
    std::cerr << "Runtime error: " << e.what() << std::endl;
    } catch (const std::exception& e) {
    // Handle any other standard exception
    std::cerr << "Standard exception: " << e.what() << std::endl;
    } catch (...) {
    // Handle any other non-standard exception (ellipsis catch-all)
    std::cerr << "Unknown exception caught." << std::endl;
    }
  6. 自定义异常: 如果标准异常类不能充分描述你的特定错误情况,可以从 std::exception 或其适当的派生类(如 std::runtime_error)派生出自定义异常类。
  7. class MyCustomError : public std::runtime_error {
    public:
    MyCustomError(const std::string& message) : std::runtime_error(message) {}
    // Optionally add more members or methods specific to this error
    };

    // throw MyCustomError("A very specific problem occurred.");

示例代码

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> // Key header for these exceptions

// Function that might throw various stdexcept exceptions
void process_data(int val, const std::vector<std::string>& data, size_t index) {
    if (val == 0) {
        throw std::invalid_argument("Input value 'val' cannot be zero.");
    }
    if (val < 0) {
        throw std::domain_error("Input value 'val' must be positive for this operation.");
    }

    if (data.empty() && index == 0) {
        // This might be okay or an error depending on context.
        // For demonstration, let's say it's an error if we expect data.
    }

    if (index >= data.size()) {
        // Constructing a more informative message
        std::string err_msg = "Index " + std::to_string(index) +
                              " is out of range for data size " + std::to_string(data.size());
        throw std::out_of_range(err_msg);
    }

    if (data.at(index).length() > 1000) { // Assuming a hypothetical length limit
        throw std::length_error("String at index is too long.");
    }

    // Simulate a runtime condition that might fail
    if (val == 42) { // Arbitrary condition for runtime_error
        throw std::runtime_error("Encountered an unexpected runtime condition with value 42.");
    }

    std::cout << "Processing data with val=" << val << " at index=" << index << ": " << data.at(index) << std::endl;
}

int main() {
    std::vector<std::string> my_data = {"apple", "banana", "cherry"};

    try {
        process_data(10, my_data, 1); // Should succeed
        // process_data(0, my_data, 0);  // Throws std::invalid_argument
        // process_data(-5, my_data, 0); // Throws std::domain_error
        // process_data(10, my_data, 5); // Throws std::out_of_range
        std::vector<std::string> large_string_data = {std::string(2000, 'x')};
        // process_data(10, large_string_data, 0); // Throws std::length_error
        process_data(42, my_data, 0); // Throws std::runtime_error

    } catch (const std::invalid_argument& e) {
        std::cerr << "Caught invalid_argument: " << e.what() << std::endl;
    } catch (const std::domain_error& e) {
        std::cerr << "Caught domain_error: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Caught out_of_range: " << e.what() << std::endl;
    } catch (const std::length_error& e) {
        std::cerr << "Caught length_error: " << e.what() << std::endl;
    } catch (const std::logic_error& e) { // Catches any other logic_error
        std::cerr << "Caught other logic_error: " << e.what() << std::endl;
    } catch (const std::runtime_error& e) { // Catches runtime_error and its derivatives (if any not caught above)
        std::cerr << "Caught runtime_error: " << e.what() << std::endl;
    } catch (const std::exception& e) { // Catches any other std::exception
        std::cerr << "Caught other std::exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "Caught an unknown exception." << std::endl;
    }

    return 0;
}

总结

<stdexcept> 提供的标准异常类是C++错误处理机制的重要组成部分。它们为常见的错误场景定义了一致的、可识别的类型,使得代码的健壮性和可维护性得以提高。

  • std::logic_error 及其派生类(domain_error, invalid_argument, length_error, out_of_range)用于报告通常可以在编码阶段避免的逻辑错误。
  • std::runtime_error 及其派生类(range_error, overflow_error, underflow_error)用于报告通常在运行时才能检测到的错误。

通过合理地抛出和捕获这些标准异常,开发者可以编写出更清晰、更易于调试和维护的C++代码。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4