stdexcept介绍与使用指南
1. 概述
在 C++ 中,异常处理(Exception Handling)是一种重要的错误处理机制。当程序运行中遇到无法继续的错误时,可以通过 抛出异常(throw) 来中断当前流程,并让上层调用者决定如何处理。
C++ 标准库 <stdexcept>
定义了一组常用的异常类,这些类位于 std
命名空间中,方便我们在需要抛出常见类型的异常时直接使用,而无需自己定义异常类型。
头文件位置
#include <stdexcept>
2. <stdexcept>
中常用的异常类
类名 | 继承关系 | 用途说明 |
---|---|---|
std::exception | 最顶层基类(定义在 <exception> 中) | 所有标准异常的基类 |
std::logic_error | 继承自 std::exception | 表示逻辑错误,通常是程序员的错误,运行前应能避免 |
std::runtime_error | 继承自 std::exception | 表示运行时错误,通常是外部环境因素造成 |
std::invalid_argument | 继承自 std::logic_error | 参数无效 |
std::domain_error | 继承自 std::logic_error | 数学定义域错误 |
std::length_error | 继承自 std::logic_error | 长度超出允许范围 |
std::out_of_range | 继承自 std::logic_error | 越界访问 |
std::range_error | 继承自 std::runtime_error | 运行时范围错误 |
std::overflow_error | 继承自 std::runtime_error | 数值溢出 |
std::underflow_error | 继承自 std::runtime_error | 数值下溢 |
继承层级结构可以表示为:
std::exception├── std::logic_error│ ├── std::invalid_argument│ ├── std::domain_error│ ├── std::length_error│ └── std::out_of_range└── std::runtime_error├── std::range_error├── std::overflow_error└── std::underflow_error
3. 构造与用法
每种异常类的构造函数通常接受一个 std::string
或 const char*
作为错误信息。例如:
throw std::invalid_argument("Parameter must not be negative");
4. 基本使用示例
#include <iostream>
#include <stdexcept>double safe_divide(double a, double b) {if (b == 0) {throw std::invalid_argument("Division by zero is not allowed");}return a / b;
}int main() {try {std::cout << safe_divide(10, 0) << "\n";}catch (const std::invalid_argument &e) {std::cerr << "Invalid argument: " << e.what() << "\n";}catch (const std::exception &e) { // 捕获其他标准异常std::cerr << "Error: " << e.what() << "\n";}return 0;
}
运行结果
Invalid argument: Division by zero is not allowed
5. 分类与选用原则
-
逻辑错误(
std::logic_error
及其派生类)
通常在 程序逻辑错误 时抛出,例如:- 传入函数的参数无效(
invalid_argument
) - 索引越界(
out_of_range
) - 违反数学定义域(
domain_error
) - 数据结构的长度超限(
length_error
)
- 传入函数的参数无效(
-
运行时错误(
std::runtime_error
及其派生类)
通常在 外部不可控因素 造成问题时抛出,例如:- 文件读取失败
- 内存不足
- 数值溢出(
overflow_error
) - 范围计算错误(
range_error
)
6. 捕获异常的注意事项
-
总是按引用捕获异常对象,避免对象切割:
catch (const std::out_of_range& e) { ... }
-
按派生类到基类的顺序捕获,因为捕获顺序是从上到下匹配的:
try { ... } catch (const std::out_of_range& e) { ... } catch (const std::logic_error& e) { ... } // 父类 catch (const std::exception& e) { ... } // 最通用
-
适时重抛(
throw;
)以让更高层来处理异常。
7. 自定义异常与 <stdexcept>
结合
如果标准异常不足以表达业务含义,可以从 <stdexcept>
的异常类派生自定义类型:
class MyAppError : public std::runtime_error {
public:explicit MyAppError(const std::string& msg): std::runtime_error("MyAppError: " + msg) {}
};
这样做的好处是既能保留标准异常的行为,又能在需要时用 catch (const MyAppError&)
精确捕获。
8. 总结
<stdexcept>
提供了一套常用的异常类,覆盖了大多数逻辑错误与运行时错误场景。- 充分使用这些现成的异常类,可提升错误信息的语义性与代码可读性。
- 对于不符合现有分类的特殊错误,可以继承这些类来自定义异常。
- 使用异常处理应当注意捕获顺序、按引用捕获,以及合适的错误分类。
第二个问题
根据我的了解,像是索引越界这种异常一般如果不走.at()的话,对于数组的直接越界访问是会直接程序终止而不会抛出异常的,引入会有什么不同吗,不是仍然需要手动抛出吗
你说得完全正确,C++ 里大部分容器或原生数组的越界访问,默认是不会自动抛出 <stdexcept>
异常的,这个问题很容易让初学者误解。
我可以详细解释一下为什么,以及 <stdexcept>
在这种场景下到底能做什么。
1. 原生数组的越界访问
假设你用的是 C 风格数组:
int arr[3] = {1, 2, 3};
arr[5] = 10; // 越界写
- 不会自动抛出异常
原生数组在编译器或运行时没有边界检查机制,越界访问会导致未定义行为(Undefined Behavior)。 - 未定义行为可能是
- 修改了无关内存
- 程序直接崩溃(Segmentation fault)
- 看似“正常”地运行但隐藏 bug
<stdexcept>
并不会改变这一行为,因为它只是提供了异常类,并不自动插入边界检查逻辑。
2. std::vector
和 .at()
的区别
在标准容器 std::vector
中:
#include <vector>std::vector<int> v = {1, 2, 3};
v[5] = 10; // 未定义行为,不抛异常
v.at(5) = 10; // 抛出 std::out_of_range 异常
operator[]
不进行边界检查(出于性能考虑).at()
会进行边界检查,如果越界就主动抛出std::out_of_range
(定义在<stdexcept>
里)
这是 <stdexcept>
在标准容器中的一个真实应用场景:标准库内部用它来描述和传递容器操作的逻辑错误。
3. 如果是你自己的数据结构
如果你实现了自己的数据结构,比如自定义动态数组,你需要手动在检测到越界时抛出 <stdexcept>
的异常:
#include <stdexcept>class MyArray {
public:MyArray(size_t size) : size_(size), data_(new int[size]) {}~MyArray() { delete[] data_; }int& at(size_t index) {if (index >= size_) {throw std::out_of_range("Index out of range");}return data_[index];}private:size_t size_;int* data_;
};
这里的 throw std::out_of_range(...)
就是你手动利用 <stdexcept>
提供的异常类。
4. <stdexcept>
究竟改变了什么?
- 它不会自动让原生数组或
operator[]
越界时抛异常 - 它的作用是提供统一的异常类接口和层次结构
- 这些类在标准容器实现和你自己的代码里都可以复用
- 比如
.at()
的实现内部就是用throw std::out_of_range(...)
- 如果你想让某个 API 在出现逻辑或运行时错误时抛出统一风格的错误,去
<stdexcept>
里找对应的异常类更规范,更可读
5. 总结
- 引入
<stdexcept>
不会改变原本的越界行为,C 风格数组和不做检查的容器操作依然会直接触发 UB(可能崩溃)。 <stdexcept>
是用来定义异常类型的库,是否抛出这些异常完全由代码逻辑(你或标准容器的实现)决定。- 如果你希望在越界时得到可控的错误处理,就需要:
- 使用容器的
.at()
方法(内部自动抛出异常) - 或者自己手动写边界检查并
throw
对应的<stdexcept>
异常。
- 使用容器的