如何对抛出哪个标准异常做出明智的决定

How to make an intelligent decision about which standard exception to throw?

本文关键字:决定 异常 标准      更新时间:2023-10-16

我们知道异常类有两个派生类:logic_errorruntime_error

logic_error有四个派生类:domain_errorinvalid_argumentlength_errorout_of_range

runtime_error有三个派生类:range_erroroverflow_errorunderflow_error

虽然其中一些是不言自明的,如overflow_errorunderflow_error,有些不是那么清楚,特别是range_error,无论是MSDN和cplusplus只是说"报告范围错误",这是接近说什么,它是如何不同的out_of_rangedomain_error??

另一个问题是当我抛出异常时,我应该选择哪一个?例如,在reverse_string(char* s)中,当s为NULL时抛出哪个异常?在float calc_ellipse_area(float a, float b),当a或b是<=0时抛出?当a == b(严格地说,圆不是椭圆!)

最后,实际上,如果我抛出一个没有正确分类的异常真的有关系吗?

逻辑错误(理论上)是程序员错误的结果。运行时错误是程序员不容易避免的。

当你编写一个函数时,记录它的先决条件和/或假设是很有用的。如果这些前提条件被打破,这就是一个逻辑错误。

运行时错误通常是由外部原因引起的:文件操作失败,打印机脱机,无法加载DLL。

如果一个路径参数是错误的,这是一个逻辑错误。如果它是一个有效的路径字符串,但不存在,或者您没有访问它的权限,那将是一个运行时错误。

有时它变得武断。错误的用户输入可能是运行时错误,但未能验证用户输入更多的是逻辑错误。在特定的情况下,这可能并不明显。

如果某事开始于2011年2月1日,结束日期"01 Jan 2011"是无效参数,而"31 Feb 2011"则超出范围。"炸鱼薯条"的完成日期是一个域错误。长度错误通常与缓冲区大小有关,但也可能包括输入数据过多或过少或诸如此类的错误。

范围错误类似于超出范围错误,除了上下文(运行时,而不是逻辑)。例:可用打印机数量= 0。溢出和下溢或多或少是不言自明的。

最后,以你和你的同事觉得有意义的方式使用它们——或者根本不使用它们。有些人在所有情况下都使用std::exception

只有当你打算以不同的方式处理不同的异常时,这才是真正重要的。如果您只是打算显示(或记录)一条消息并继续,那么使用什么异常并不重要。

例如,在reverse_string(char* s)中,当s为NULL时抛出哪个异常?

在float calc_ellipse_area(float a, float b)中,当a或b是<=0时抛出哪个?当a == b(严格地说,圆不是椭圆!)

对于这两个,使用std::invalid_argument

或者您可以定义自己的异常,称为null_argument,从std::logic_error(或从std::invalid_argument)派生,并将其用于NULL参数。

关键是,如果没有一个标准异常类适用于您的情况,或者您希望更多特定的异常,则定义一个从现有类派生的异常。

例如,如果您想在遇到无效索引时抛出异常,那么您可以使用std::out_of_range 或定义从std::out_of_range派生的更具体的类index_out_of_range

如果我抛出一个没有正确分类的异常真的重要吗?

是的,这很重要。例如,它增加了代码的可读性。如果在遇到无效索引时抛出std::logic_error,那么它不会增加多少可读性,但是如果抛出std::out_of_range ,则会大大提高可读性。如果你输入index_out_of_range,它会增加更多,因为它更具体。

out_of_range和range_error的区别在它们的父类的描述中:

logic_error:该类定义要报告的异常抛出对象的类型程序的内部逻辑错误。这些都是理论上的可以预防的。

runtime_error:该类定义要报告的异常抛出对象的类型只能在运行时确定的错误。

域误差是专门针对数学函数的。所以你的calc_ellipse_area可以在负值时抛出一个域错误(如果一个或两个参数都是0,则返回0)。如果椭圆碰巧也是一个圆,我没有任何理由抱怨,就像矩形面积函数在正方形上失败一样。

将null指针传递给不应该接收null的函数,我将通过无效参数异常处理。

c++允许你抛出任何你喜欢的东西。对于那些可能使用您的代码的人来说,真正有帮助的是,您在方法签名之后命名您抛出的内容,并且这些名称具有合理的描述性。默认情况下,函数可以抛出任何东西——为了保证函数不会抛出,你必须在签名中使用空的throw()。

从标准:

  • 标准c++库提供了用于报告c++程序中某些错误的类(17.6.5.12)。在这些类反映的错误模型中,错误分为两大类:逻辑错误和运行时错误。
  • 逻辑错误的区别特征是它们是由于程序内部逻辑的错误造成的。理论上,它们是可以预防的。
  • 相比之下,运行时错误是由超出程序范围的事件引起的。它们无法轻易提前预测。

然而,所有来自runtime_error的异常类型都被错误分类——它们都很容易预防。

对于实际的运行时错误,c++标准库是相当不一致的,它有时使用返回值或内部状态(例如iostream::bad())。当它确实使用异常时,它们不会从runtime_error派生。例如,std::bad_allocstd::exception的直接子类。

总之,您不应该使用std::runtime_error或其任何预定义的子类。