我应该抛出异常吗?

6

我是一名C#程序员,但现在想更深入地学习C++。
我了解C++的基础知识,但不知道如何处理错误。

例如:我正在编写一个库。我创建了一个构造函数,要求一个整数作为参数。
如果该整数大于50,则发生错误。在C#中,我会抛出ArgumentOutOfRange异常,但在C++中应该怎么做呢?


可能是如何在C++中处理构造函数失败?的重复问题。 - BЈовић
@stefan,在C++中,人们不使用异常来控制程序流程。远离问题——制作一个工厂可能是个好主意,它可以执行所有必要的检查并避免出于错误原因使用异常。 - ShPavel
4个回答

22
在C#中,我会抛出ArgumentOutOfRange异常,但在C++中该怎么办?
首先,你应该考虑这是否应该是函数的{前提条件},将检查值是否在范围内的责任留给调用者。
如果你决定采取这个选项,那么使用超出范围的值调用函数将是未定义的行为,在函数内部,你可以只使用调试断言来帮助你发现可能的误用,而不需要抛出任何异常。
另一方面,如果你决定函数应该具有广泛的契约,并在参数超出允许范围时以明确定义的方式对其做出反应,则可以抛出std::out_of_range异常。
例如:我正在编写一个库[...]。
如果你正在编写一个库,意味着你不知道客户端在性能和稳健性方面的确切要求,你可以考虑提供两个此类函数——一个具有广泛契约,抛出异常,另一个具有狭窄契约,假定客户端提供有意义的输入。
这样,你的库的用户可以根据他们的使用情况来决定是否需要每次调用函数时检查输入正确性的开销。例如,C++标准库采用了这种策略,为std::vector提供了一个非抛出异常的operator[],用于基于索引访问集合中的元素(如果索引越界,则此函数的行为未定义),以及一个成员函数at(),它执行索引检查并在索引越界时抛出异常。

4
全方位优秀文章。+1 肯定(前/后置条件),std::vector 示例和选项。 - Freddy
@Freddy:很高兴你发现它有帮助。 - Andy Prowl
1
你对异常的长而绝对正确的回答可能会让OP走向错误的方向。在这种情况下,应考虑Kerrek SB的下一个答案,以避免像@stefan对原始问题的评论。 - ShPavel
什么是窄合同或宽合同? - Pedro Reis
@AndyProwl 通过(https://dev59.com/al0a5IYBdhLWcg3whJHW)我认为,具有广泛契约的函数永远不会出现未定义行为(因此,它永远不会抛出异常?),而狭窄的函数可能会根据调用条件而不确定。但是你写道:“具有广泛契约并引发异常的函数”...你能解释一下吗?谢谢! - Pedro Reis

4

这取决于是否有可能在正常程序流程中传递大于50的整数给构造函数,或者这是一个异常情况。但通常情况下,唯一使对象构建失败的方法是抛出异常。

您的用户代码可能如下所示:

int n = parse_user_input()

if (n < 50)
{
    Foo x(n);
    x.do_cool_stuff();
}
else
{
    // report user error
}

也就是说,在正常的控制流程中,你不会真正使用异常。在这种代码模式下,如果参数超出范围,Foo::Foo(int) 抛出异常是完全可以接受的。
你可以在<stdexcept>中找到一些有用的标准异常类。

3
与C#中相同:抛出异常。这是防止对象构建的唯一方法。 std::invalid_argument 是一个很好的标准选择来抛出异常。

0

来自C++ FAQ: [17.8] 如何处理构造函数失败的情况?

摘录:

抛出异常。

构造函数没有返回类型,因此无法使用返回代码。因此,最好的方法是通过抛出异常来表示构造函数失败。如果您无法使用异常选项,则“最不好”的解决方法是通过设置内部状态位将对象置于“僵尸”状态,以便对象表现得像已经死亡一样,即使它在技术上仍然活着。

因此,在您的情况下,抛出std::invalid_argumentstd::out_of_range将是完全可以接受的。如果在您的情况下这有益处,您还可以抛出自定义异常。在C++ FAQ中,请参见:[17.12] 我应该抛出什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接