为什么使用异常而不是返回错误代码

时间:2010-10-03 15:16:39

标签: c++ exception

  

可能重复:
  Exceptions or error codes

您好,

我正在寻找关于何时使用返回值v / s例外的一些指导原则。

非常感谢提前。

8 个答案:

答案 0 :(得分:11)

错误通常在代码中检测到非常低的级别,但处理的级别非常高。如果对这些使用返回代码,则必须设计所有中间级别以期望低级代码并将其传播直到处理它。除了例外,你只需要抛出低水平,并抓住高水平(如果有的话)。只要您的中级代码使用RAII,您就不必更改其中任何一个来进行错误传播。

所以我经常考虑错误的类型以及错误处理的位置。如果调用者可能会处理问题,因为它预计会是常见的事情,那么返回代码就很棒了。如果错误是灾难性的(无法分配必要的资源),那么不能指望直接调用者处理它。

需要考虑的其他事项:异常不能忽略,返回代码可以。如果问题无法处理会很危险,请使用例外。

从技术上讲,异常不能通过C代码传播回来。 (它适用于许多系统,但它不可移植也不保证。)因此,如果你有一个C库回调你的C ++代码,你可能不应该从回调中抛出异常。同样适用于线程边界等

答案 1 :(得分:4)

其中一些可能会重复内容,但这里有一个简单的提示,即使用其中一个:

没有适当的哨兵值

您的函数可能无法使用sentinel值来发出错误信号,因为函数将所有可能的值用作有效答案。这可能发生在不同的情况下,最明显的是整数数值算法。 0-1通常用作特殊值,但某些相当常见的算法(包括pow)可能无法使用这些算法(即0^1=0-1^3=-1) 。因此,选择适当的值在某种程度上是一门艺术,同一性是一个问题,用户必须记住每个特例的错误值。

有些API认识到这一点,并且(几乎)从不使用真正的返回值,但始终依赖于返回引用语义,并将所有函数中的返回值用作状态代码,是某些(标准)“成功”值,或特定于功能的错误代码。例如,CUDA就有这样的惯例。

默认传播

错误代码通常被称为“更有效”(其中一个答案的评论解释了为什么这不一定是真的)。但是,他们遇到了两个常见问题。

  1. 您必须手动将错误传播到调用堆栈中。这通常被省略(特别是在示例代码和书籍中,这非常令人恼火),因为它使用繁琐且容易出错的处理代码来填充代码。
  2. Erros被高度稀释,因为不同API之间的错误代码通常是不可能的。此外,设计您自己的错误代码涵盖所有库的错误代码的并集是一项艰巨的任务。这就是为什么在许多应用程序中,您得到The operation failed.消息而不是Ran out of disk space.
  3. 要解决(1),默认情况下会传播异常。故意忽略错误在代码中变得明显,而不是隐藏。为了解决(2),异常使用类型系统,阻止您编译具有冲突错误“值”的程序。此外,使用异常类层次结构可以表示相关结果的“族”。 (注意:这经常被误用,人们抓住Exception而不是NoMoreDiskSpace并仍显示通用的The operation failed.消息。

    一致性问题

    有些人会在他们的应用程序中推荐两种混合物,但恕我直言,这导致两种系统都被误用的情况。在这些混合约定中,通常不会捕获异常,并且由于混淆而不会检查错误代码。一方面,因为异常仅用于异常情况,所以假设它们从不发生,或者根本无法处理。另一方面,假设返回错误代码的失败是次要的并且根本不处理。当然,由每个程序员决定情况是否异常,导致在要检查的错误代码和要捕获的异常之间存在很多混淆。

    您选择哪种系统,请务必充分利用它,并保持其一致性。

答案 2 :(得分:2)

您可以查看异常并返回值作为调用者和被调用者之间的两种不同通信方法。返回值是很好的快速通知父母的东西,例外是粗鲁的堂兄,你可以解雇,让其他人知道“在那里”有多糟糕。

如果您编写的代码试图处理所有情况,您将使用返回值,错误代码等填写它。如果您忽略处理调用堆栈上发生日志的某些情况,并希望快速解决此问题 - 有人会在此时显示MessageBox - 但你可以抛出,并在上面的任何适当级别处理它。 也许这就是你问题的答案!

第二个想法,这里有一些可能适用的规则:

  • 使用bool返回值来表示简单的成功/失败情况
  • 使用枚举返回值来处理更复杂的情况
  • 使用ret。彼此了解的类的值,例如,用于相同目的或属于同一模块的类
  • 使用例外来远远地解决不可预见的情况。将以前没有想到的系统错误转换为您可以在更高级别处理的某些例外情况

我猜这两种方法都待在这里。随着时间的推移,你的判断将说明每次使用哪一个。

答案 3 :(得分:1)

抛出异常时会产生性能影响,这是错误代码用于更频繁的错误情况的原因。真正存在异常问题的例外情况。

例如 - 在STL中,如果找不到匹配项,则std::find不会抛出。但是如果内存耗尽,vector::push-back会抛出。

答案 4 :(得分:1)

C ++异常应该仅用于异常错误。

当错误非常糟糕以至于调用函数不能或者实际上不应该处理错误时,它们特别有用。内存耗尽。抛出的异常展开堆栈直到它看到的第一个try块(或者可能是第一个带有适当catch的try块,这会更有意义)。这允许您从深度嵌套的函数调用中捕获异常错误,并确保使用try块将堆栈清理为函数。

这一切都带有开销,所以当你可以处理调用例程中的(例外)错误时,请使用返回码。

答案 5 :(得分:1)

除了bjskishore123在其评论中与之相关的问题中已经提出的讨论之外,Ned BatchelderJoel Spolsky之间有一个非常有趣的历史来回。你可能不完全同意奈德,但他的论点经过深思熟虑,值得一读。

答案 6 :(得分:1)

如果你的函数没有任何东西可以返回,生活很简单 - 它可以返回成功/失败代码。但是当你的函数已经对其返回值有意义时,你需要使用“信号值”来返回错误代码。查找返回-1的方法,例如,使用信号值。有时几乎不可能提出良好的信号值。如果函数返回下一个可用日期,那么当没有可用日期时它会返回什么?真的没有“-1”类型的日期。所以这是异常是一种非常有用的机制的一种情况。 (这也处理不返回任何内容的构造函数。)

是的,抛出和捕获异常比检查错误代码要贵一些。但它不能被遗忘,许多人发现它更具表现力。并且,正如其他答案中所提到的,当存在一系列函数调用时,让所有中间调用检查返回值并将其传递回链中是很尴尬的。

答案 7 :(得分:0)

当出现问题并不常见时,请使用错误代码。例如,打开文件或建立网络连接的代码不应返回异常 - 这是非常常见的问题。

当出现问题的异常时使用异常 - 失败确实非常特殊。

所以,这可以简化为:

  • 大多数时候使用错误代码。