代码之家  ›  专栏  ›  技术社区  ›  KPexEA

在C++中,使用异常和Test/catch来代替错误代码有什么好处?

  •  23
  • KPexEA  · 技术社区  · 16 年前

    我已经编程C和C++很长时间了,到目前为止我还从来没有使用过异常和尝试/捕获。使用它而不是让函数返回错误代码有什么好处?

    13 回复  |  直到 16 年前
        1
  •  29
  •   Martin    16 年前

    需要以某种方式确认一个异常——如果不积极采取措施,就不能默默地忽略它。

        2
  •  27
  •   James Curran    16 年前

    例外的好处有两个方面:

    • 你必须在某种程度上与他们打交道,否则他们将终止你的计划。对于错误代码,您必须显式检查它们,否则它们将丢失。

    • 它们可以被忽略。

        3
  •  17
  •   Salman A    16 年前

    优点是您不必在每次可能失败的调用后检查错误代码。但为了使其正常工作,您需要将其与RAII类相结合,以便在堆栈展开时自动清理所有内容。

    出现以下错误消息:

    int DoSomeThings()
    {
        int error = 0;
        HandleA hA;
        error = CreateAObject(&ha);
        if (error)
           goto cleanUpFailedA;
    
        HandleB hB;
        error = CreateBObjectWithA(hA, &hB);
        if (error)
           goto cleanUpFailedB;
    
        HandleC hC;
        error = CreateCObjectWithA(hB, &hC);
        if (error)
           goto cleanUpFailedC;
    
        ...
    
        cleanUpFailedC:
           DeleteCObject(hC);
        cleanUpFailedB:
           DeleteBObject(hB);
        cleanUpFailedA:
           DeleteAObject(hA);
    
        return error;
    }
    

    除了例外和RAII

    void DoSomeThings()
    {
        RAIIHandleA hA = CreateAObject();
        RAIIHandleB hB = CreateBObjectWithA(hA);
        RAIIHandleC hC = CreateCObjectWithB(hB);
        ...
    }
    
    struct RAIIHandleA
    {
        HandleA Handle;
        RAIIHandleA(HandleA handle) : Handle(handle) {}
        ~RAIIHandleA() { DeleteAObject(Handle); }
    }
    ...
    

    乍一看,RAII/Exceptions版本似乎更长,直到您意识到清理代码只需要编写一次(有很多方法可以简化)。但是DoSomeThings的第二个版本更清晰,更易于维护。

    不要尝试在没有RaIII习惯用法的C++中使用异常,因为这样会泄漏资源和内存。所有清理都需要在堆栈分配对象的析构函数中完成。

    我意识到还有其他方法来处理错误代码,但它们最终看起来都是一样的。如果你放弃gotos,你最终会重复清理代码。

    错误代码的一个要点是,它们明确了哪些地方会失败,以及如何失败。在上面的代码中,您编写它时假设事情不会失败(但如果失败了,您将受到RAII包装器的保护)。但最终你会对可能出现的问题漠不关心。

        4
  •  10
  •   Bill the Lizard    16 年前

        5
  •  8
  •   Gerald    16 年前

    除了前面提到的其他事情之外,您不能从构造函数返回错误代码。也可以使用析构函数,但也应避免从析构函数引发异常。

        6
  •  7
  •   Steven A. Lowe    16 年前
    • 当出现错误条件时,返回错误代码 在某些情况下
    • 当出现错误条件时引发异常 在任何情况下都应如此

    在前一种情况下,函数的调用方必须检查预期故障的错误代码;在后一种情况下,异常可以由堆栈上的任何调用方(或默认处理程序)酌情处理

        7
  •  5
  •   Anthony Williams    16 年前

    我写了一篇关于这个的博客( Exceptions make for Elegant Code Overload . 我写这篇文章是为了回应Joel在StackOverflow播客上说的话!

    无论如何,我坚信在大多数情况下,异常比错误代码更可取。我发现使用返回错误代码的函数非常痛苦:每次调用后都必须检查错误代码,这可能会中断调用代码的流程。这也意味着不能使用重载运算符,因为无法发出错误信号。

    检查错误代码的痛苦意味着人们经常忽略这样做,从而使它们完全没有意义:至少你必须用 catch 陈述

    if 语句、大量重复的清理代码或 goto 在函数末尾调用公共清理块。这些选择都不是令人愉快的。

        8
  •  2
  •   Kevin Little    16 年前

    Here's 对EAFP的一个很好的解释(“请求原谅比允许更容易”),我认为这在这里适用,即使它是维基百科中的Python页面。在我看来,使用异常会导致一种更自然的编码风格——在许多其他人看来也是如此。

        9
  •  2
  •   Uri    16 年前

    如果没有例外,您必须从每个调用中获取一个返回值,并确保它仍然是合法的。

        10
  •  2
  •   cdleary    16 年前

    Google's C++ Style Guide 对C++代码中异常使用的优缺点进行了深入、深入的分析。它还指出了一些你应该问的更大的问题;i、 e.我是否打算将我的代码分发给其他人(他们可能难以与启用异常的代码库集成)?

        11
  •  1
  •   wilhelmtell    16 年前

    有时,为了标记异常情况,您确实必须使用异常。例如,如果构造函数中出现了错误,并且您发现将此情况通知调用方是有意义的,那么您别无选择,只能抛出异常。

    另一个例子:有时函数无法返回值来表示错误;函数可能返回的任何值都表示成功。

    int divide(int a, int b)
    {
        if( b == 0 )
            // then what?  no integer can be used for an error flag!
        else
            return a / b;
    }
    
        12
  •  1
  •   QBziZ    16 年前

    您可以创建一个基本错误类来检查其数据类型或是否调用了某个方法(例如IsOk)。如果没有,您可以记录一些内容,然后退出,或抛出异常,或引发断言,等等。。。

    这两种说法都会显示出程序员同样缺乏善意。

    但是,同样地,函数或方法应该始终使用它可以生成的异常进行注释。所以基本上你必须解决同样的问题,没有一个接口来支持它。

        13
  •  0
  •   Mark Kegel    16 年前