C ++ - 返回代码异常的参数

时间:2009-12-04 20:34:22

标签: c++ exception

我正在讨论在新的C ++项目中采用哪种方式。由于以下原因,我赞成异常而非返回代码(仅限特殊情况) -

  1. 构造函数无法提供返回代码
  2. 将逻辑代码清除
  3. 的故障路径(应该非常罕见)分离
  4. 在非特殊情况下更快(不检查是否数十万次)
  5. 如果有人搞砸了返回代码设置(忘记退回FAIL),可能需要很长时间才能跟踪。
  6. 错误中包含的消息提供更好的信息。 (有人向我指出,返回枚举可以对错误代码做同样的事情)
  7. 来自Jared Par 无法在没有代码的情况下忽略专门设计来处理它
  8. 这些是我从思考它和谷歌搜索中提出的要点。我必须承认,在过去的几年中,我倾向于使用C#工作的例外情况。请在退货代码上发布使用例外的进一步原因。对于那些喜欢返回代码的人,我也愿意倾听你的推理。感谢

11 个答案:

答案 0 :(得分:45)

我认为article总结了这一点。

使用例外的参数

  1. 异常将错误处理代码与正常的程序流分开,从而使代码更具可读性,健壮性和可扩展性。
  2. 抛出异常是从构造函数报告错误的唯一简洁方法。
  3. 与错误代码不同,例外很难被忽略。
  4. 异常很容易从深层嵌套的函数传播。
  5. 例外可以是,通常是用户定义的类型,它们携带的信息比错误代码多得多。
  6. 使用类型系统将异常对象与处理程序匹配。
  7. 使用例外的参数

    1. 异常通过创建多个不可见的退出点来破坏代码结构,使得代码难以阅读和检查。
    2. 异常容易导致资源泄漏,尤其是在没有内置垃圾收集器且最终阻塞的语言中。
    3. 学习编写异常安全代码很难。
    4. 例外是昂贵的,并且违背了仅为我们使用的东西付款的承诺。
    5. 很难在遗留代码中引入异常。
    6. 执行属于正常程序流程的任务时,很容易滥用异常。

答案 1 :(得分:10)

我听说过更喜欢返回代码优于异常的最佳情况就是:

  1. 编写异常安全代码 hard [在C ++中]。
  2. 由于我最近在C#中有很多经验,我可以理解你使用异常的愿望,但不幸的是C ++不是C#,我们可以在C#中解决的很多事情最终都可能致命C ++。

    the case for and against中可以找到Google's style guidelines的一个很好的总和。简而言之:

      

    优点:

         
        
    • 例外允许更高级别的应用程序决定如何操作   处理“不可能发生”的失败   深层嵌套的函数,没有   模糊和容易出错的簿记   错误代码。
    •   
    • 大多数其他现代语言都使用例外。在...中使用它们   C ++会使它更加一致   其他人使用Python,Java和C ++   熟悉。
    •   
    • 某些第三方C ++库使用异常并将其关闭   内部变得更难   与这些图书馆整合。
    •   
    • 异常是构造函数失败的唯一方法。我们可以模拟   这有一个工厂功能或   Init()方法,但这些需要堆   分配或新的“无效”状态,   分别
    •   
    • 在测试框架中,例外非常方便。
    •   
         

    缺点:

         
        
    • 当您将throw语句添加到现有函数时,您必须   检查所有可传递的呼叫者。   要么他们必须至少做到   基本的异常安全保障,或   他们绝不能抓住例外   并对该计划感到满意   终结了。例如,   如果f()调用g()调用h()和h   抛出一个异常,抓住了,g   必须小心,否则可能不干净   正确的。
    •   
    • 更一般地说,例外使得程序的控制流程变得困难   通过查看代码进行评估:   函数可能会返回到您的位置   不要指望。结果   可维护性和调试   困难。你可以减少这个   通过一些关于如何和在何处的规则来花费   例外可以使用,但在   开发人员需要的更多成本   了解和理解。
    •   
    • 异常安全需要RAII和不同的编码实践。   需要大量的配套机械   使编写正确的异常安全   代码很简单。此外,要避免要求   读者了解整个电话   图,异常安全的代码必须   隔离写入的逻辑   持久状态变为“提交”   相。这将有两个好处   和成本(也许你被迫的地方   混淆代码来隔离   承诺)。允许例外会   迫使我们总是支付这些费用   即使它们不值得。
    •   
    • 启用异常会为每个生成的二进制文件添加数据,从而增加   编译时间(可能稍微)和   可能增加地址空间   压力。
    •   
    • 异常的可用性可能会鼓励开发人员抛弃它们   当他们不合适或   当它不安全时从它们身上恢复   这样做。例如,用户无效   输入不应该导致异常   被抛出我们需要制作   样式指南甚至更长的文档   这些限制!
    •   

    我建议阅读并理解利弊,然后根据这些来决定你自己的项目。你没有google所拥有的相同软件,所以对他们来说有意义的可能对你没有意义(这就是为什么我省略了他们的结论)。

答案 2 :(得分:6)

恕我直言,偏好返回代码的首要原因是你不能无声地忽略异常。这样做至少需要额外的最少代码。

答案 3 :(得分:4)

针对异常错误情况使用例外。你有一些很好的论据,我想反对一些争论。

首先,标准C ++库本身就使用了异常。如果没有容器类或iostream,则不能使用它们。由于很多有用的功能都会使用它们,试图在没有它们的情况下相处会产生很多问题。

其次,一旦学会了如何编写异常安全代码就不难了。它需要一致的RAII,但这就是你应该写的方式。您应该采用构造提交方法,但这通常是一个优点,并避免一些微妙的错误。 (例如,自我分配问题完全通过复制交换方法消失。)根据我的经验,异常安全代码通常看起来更好。这是C ++程序员必须学习的东西,但C ++程序员必须学习很多东西,而且这还不止于此。

第三,如果您限制特殊情况的例外,则应该minimal effects on performance。而且,正如Pavel Minaev指出的那样,如果必须将错误代码与结果一起传递,则可能会对性能产生影响,因为C ++没有设置为容易返回多个值。

第四,确实很难使旧代码异常安全。但是,这是一个新项目。

所以,我认为没有充分的理由不在特殊情况下抛出异常,并且有很多理由这样做。

答案 4 :(得分:3)

  

非特殊情况下更快(不检查是否数十万次)

在非例外情​​况下,确定已返回E_SUCCESS是一次比较。

  

如果有人搞砸了返回代码设置(忘记退回FAIL),可能需要很长时间才能追踪。

如果某人未能检查异常,则在您实际收到异常之前可能很难注意到。如果您正在处理错误代码,只需查看它是否正在检查错误代码。

答案 5 :(得分:3)

使用有意义的东西。我认为两者都有一席之地。在某些情况下,几乎不可能使用错误代码(例如,从构造函数返回失败)

其他时候,错误代码更方便。在您期望它们发生的情况下,它们更容易处理。例外情况是异常错误 - 那些不应该发生的错误,但可能会在蓝色月亮中发生。错误代码对于预期会定期发生的错误更加方便,并且可以在本地处理。在必须在调用堆栈中进一步处理错误的情况下,异常最有用。

此外,在非例外情​​况下,例外不一定更快。通常,它们需要函数prolog和epilogs中的额外异常处理代码,每次调用函数时都必须执行这些代码,无论它是否抛出异常。

答案 6 :(得分:3)

作为一般规则。当可以恢复时,预期然后使用返回码。

当无法恢复或不需要时,请使用例外。

错误处理很困难,编写带有和没有例外的干净代码是困难的。

由于这是一个新项目,您不必担心使旧代码异常安全,但是您必须担心编写干净清晰的代码。

在适当的情况下使用例外。

答案 7 :(得分:3)

由于许多其他人已经提供了使用异常而不是错误代码的技术原因,我将给出一个实用的原因。

我在使用返回代码而不是异常的复杂系统上工作。现在,这是设计良好的代码,但我敢打赌,平均而言,每个函数中大约70%的代码是错误处理代码。典型的函数看起来像这样:

long Foo( )
{
    long retCode = MoveStage( );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    int someConfigVar = 0;
    long retCode = GetConfigVar( "SomeVarName", someConfigVar );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    long retCode = DoSomething( );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    // and on and on and on...
}

代码充满了这一点,很难遵循。最重要的是,在许多地方,返回代码被完全忽略,因为我们知道调用不会失败。每个函数都返回一个ret代码,因此通常返回的函数必须作为out参数返回。此外,所有这些函数只会在出错时返回retCode,因此如果发生错误,我们只会将该死的retCode冒充到顶部。你不能用这种方式集中你的错误处理策略,它会变得一团糟。

答案 8 :(得分:2)

编写异常安全代码非常困难。一个完全人为的例子是: -

void Class::Method()
{ 
  i++;
  SomeCallThatMightThrow();
  j++;
}

将i ++和j ++替换为必须保持同步的任何两个变量,资源和状态。垃圾收集使我们不必记住配对新的和删除。具有讽刺意味的是,老式的显式返回代码测试使我们不必仔细分析可能抛出异常的每个函数,以检查它们是否与后置条件无关。

答案 9 :(得分:1)

我喜欢C ++的一个方面是,很容易想到如何根据C功能(在汇编方面易于理解)实现更高级别的功能。 C ++的例外打破了这种模式。要获得这种理解水平,我必须做很多事情。只需阅读this,您就会在理解之前花费大量时间挠头。

此外,异常要求您具有良好的纪律,使您的代码异常安全,并且资源无泄漏。这意味着将RAII用于拥有资源的任何东西..

此外,与简单的返回代码相比,当我测量它们的速度要快许多个数量级时,就会出现异常。

那么他们说你应该只抛出特殊情况,但是你如何传达非例外的,预期的,经常发生的错误。当然还有退货代码! :)

我不知道这些好处是多么值得。

答案 10 :(得分:-4)

  1. 构造函数无法提供返回代码 (除非你可能在构造函数中抛出异常,否则你会受到伤害)

  2. 将故障路径(应该非常罕见)与更清晰的逻辑代码分离 (打开一个更广泛的失败路径.C ++异常与C#完全不同。我喜欢C#异常.C ++异常比无用更糟。部分原因在于实现,部分原因是c ++是什么而不是)

  3. 在非特殊情况下更快(不检查是否/否则数十万次) (不是真的。无论如何都要检查相同数量的错误,你只是看不到它们被检查的位置。此外,还有一些重要的错误以及那些对你的代码没有或至少重要的错误,所以你不必检查每一件事(你检查是否有新的错误吗?))
  4. 如果有人搞砸了返回代码设置(忘记返回FAIL),可能需要很长时间才能追踪。 (对于C ++异常中出现的任何一个常见错误,我会犯一百万个这样的错误)
  5. 错误中包含的消息提供更好的信息。 (有人向我指出,返回枚举可以对错误代码执行相同的操作) (那么,你如何实现你的错误处理?这取决于你,不是吗?你可以有例外没有提供有用的信息或错误代码,以便在失败时注销干扰数据。在C ++中,它们似乎永远不会提供有用的信息在你真正想要他们的情况下)
  6. 来自Jared Par不可能忽略没有代码专门设计来处理它 (当然,但是这段代码通常是无用的代码。就像通过一项法律,说每个人都必须拥有无错误的程序。不管怎么说,那些不遵守这些法律的人,以及他们已经处理错误的人无论如何应该)
  7. 至于反对的原因:

    1. 希望你的程序至少在某些时候能够正常工作。
    2. 性能。
    3. 思想。如果这是一个错误,我们不应该从中“恢复”。我们要么死了一个错误信息,记录并继续,要么完全忽略它。如果我们可以修复它,那不会是一个错误,而是一个功能......如果您故意使用这样的代码,那么世界上没有任何调试工具可以帮助您。
    4. 主要是一个。