在 C++ 中选择抛出哪种异常类型,主要取决于错误的性质以及希望传达的语义信息。以下是一些指导原则,帮助在可能发生异常的地方选择合适的异常类型进行抛出:
1. std::exception
 
- 适用场景:作为所有标准异常的基类,
std::exception本身通常不直接用于抛出异常,而是作为自定义异常类的基类。 - 使用场景:如果需要定义自己的异常类,可以继承 
std::exception。 
2. std::runtime_error
 
- 适用场景:用于报告运行时错误,即在程序运行过程中由于外部条件或不可预见的情况导致的错误。
 - 常见情况: 
- 文件操作失败(如文件无法打开)。
 - 网络连接失败。
 - 动态加载库失败。
 - 其他与运行环境相关的错误。
 
 
3. std::logic_error
 
- 适用场景:用于报告逻辑错误,即程序内部的逻辑问题,通常是由于程序设计不当或输入不符合预期导致的错误。
 - 常见情况: 
- 参数验证失败(如函数参数不符合要求)。
 - 无效的状态转换。
 - 未实现的功能被调用。
 - 其他由于程序逻辑错误导致的问题。
 
 
4. std::out_of_range
 
- 适用场景:用于报告越界错误,即访问了超出有效范围的索引或位置。
 - 常见情况: 
- 访问容器(如 
std::vector、std::string)时索引超出范围。 - 访问数组时索引越界。
 
 - 访问容器(如 
 
5. std::bad_alloc
 
- 适用场景:用于报告内存分配失败的错误。
 - 常见情况: 
new操作符无法分配内存时,会抛出std::bad_alloc。- 其他内存分配相关的失败情况。
 
 
具体选择的依据
-  
错误的性质:
- 如果错误是由于运行时外部条件导致的,选择 
std::runtime_error。 - 如果错误是由于程序逻辑问题导致的,选择 
std::logic_error。 - 如果错误是由于越界访问导致的,选择 
std::out_of_range。 - 如果错误是由于内存分配失败导致的,选择 
std::bad_alloc。 
 - 如果错误是由于运行时外部条件导致的,选择 
 -  
语义清晰性:
- 选择能够最好地描述错误类型的异常类,这样可以让捕获异常的代码更容易理解错误的来源和性质。
 
 -  
代码风格和团队约定:
- 在团队开发中,遵循团队的编码规范和异常处理约定,保持一致性。
 
 
示例代码
#include <iostream>
#include <stdexcept>
#include <vector>void processFile(const std::string& filename) {// 模拟文件打开失败的运行时错误if (filename.empty()) {throw std::runtime_error("Filename is empty");}
}void validateInput(int value) {// 模拟逻辑错误,参数不符合要求if (value < 0) {throw std::logic_error("Input value cannot be negative");}
}int getElement(std::vector<int>& vec, size_t index) {// 模拟越界错误if (index >= vec.size()) {throw std::out_of_range("Index out of range");}return vec[index];
}int main() {try {processFile(""); // 可能抛出 std::runtime_error} catch (const std::runtime_error& e) {std::cout << "Runtime error: " << e.what() << std::endl;}try {validateInput(-5); // 可能抛出 std::logic_error} catch (const std::logic_error& e) {std::cout << "Logic error: " << e.what() << std::endl;}try {std::vector<int> vec = {1, 2, 3};getElement(vec, 5); // 可能抛出 std::out_of_range} catch (const std::out_of_range& e) {std::cout << "Out of range error: " << e.what() << std::endl;}return 0;
}
 
选择抛出哪种异常类型,主要依据错误的性质和希望传达的语义信息。std::runtime_error 用于运行时错误,std::logic_error 用于逻辑错误,std::out_of_range 用于越界错误,std::bad_alloc 用于内存分配错误。通过合理选择异常类型,可以使代码更具可读性和可维护性。
在C++中,选择抛出哪种标准异常类需要根据错误的类型、发生场景以及标准库的规范来判断。具体的选择依据和常见场景如下:
一、标准异常类的分类与适用场景
std::exception- 基类:所有标准异常的基类,通常不直接抛出,而是通过其派生类使用。
 - 适用场景:当需要统一捕获所有异常时,例如:
catch (const std::exception& e) {std::cerr << e.what(); // 输出错误信息 } 
std::runtime_error- 运行时错误:表示无法在编译时检测到的错误,例如内存分配失败、系统资源不足等。
 - 典型用例: 
- 内存分配失败时抛出 
std::bad_alloc(runtime_error的子类)。 - 文件操作失败(如打开不存在的文件)。
 
 - 内存分配失败时抛出 
 
std::logic_error- 逻辑错误:表示可以通过代码逻辑避免的错误,例如参数校验失败、算法逻辑错误等。
 - 常见子类: 
std::invalid_argument:参数无效(如除零操作)。std::out_of_range:索引越界(如访问容器超出范围的元素)。std::domain_error:数学运算中使用无效的输入域(如对负数开平方根)。
 
- 其他具体异常类 
std::bad_cast:dynamic_cast类型转换失败时抛出。std::bad_typeid:typeid操作符作用于NULL指针时抛出。
 
二、选择异常类的判断依据
- 错误类型是否可预见 
- 逻辑错误(
logic_error):若错误可通过代码逻辑提前检测(如参数校验),应抛出逻辑错误类。 - 运行时错误(
runtime_error):若错误无法在编译时或运行时早期检测(如内存分配失败),应抛出运行时错误类。 
 - 逻辑错误(
 - 错误的粒度 
- 优先使用具体子类:例如,参数无效时应抛出 
std::invalid_argument,而非更宽泛的std::runtime_error。 - 自定义异常类:若标准类无法准确描述错误,可继承 
std::exception自定义异常类。 
 - 优先使用具体子类:例如,参数无效时应抛出 
 - 标准库的约定 
- 遵循标准库的异常抛出规则。例如: 
std::vector::at()在越界时抛出std::out_of_range。new操作符在内存不足时抛出std::bad_alloc。
 
 - 遵循标准库的异常抛出规则。例如: 
 
三、示例代码
- 参数校验失败(逻辑错误)
void divide(int a, int b) {if (b == 0) {throw std::invalid_argument("Divisor cannot be zero");} } - 内存分配失败(运行时错误)
int* createArray(size_t size) {try {return new int[size];} catch (const std::bad_alloc& e) {std::cerr << "Memory allocation failed: " << e.what();return nullptr;} } - 索引越界(逻辑错误)
std::vector vec = {1, 2, 3}; try {int value = vec.at(5); // 抛出 std::out_of_range } catch (const std::out_of_range& e) {std::cerr << "Index out of range: " << e.what(); } 
四、最佳实践
- 优先使用标准异常类:避免重复造轮子,提高代码可读性和可维护性。
 - 明确异常边界:在函数文档中注明可能抛出的异常类型。
 - 避免过度捕获:尽量捕获具体异常类型,而非笼统的 
catch (...),以提高错误处理的精确性。
通过以上规则和示例,可以更合理地选择抛出异常的类型,使代码更加健壮和易维护。 
