2008-10-13 55 views

回答

27

可能是一个明显的观点 - 开发人员可以忽略(或不知道)你的回报状态,并继续幸福地意识到某件事失败。

需要以某种方式承认异常 - 如果不主动提供适当的位置,不能默默忽略它。

+2

您得到了以下两点之一:您可以轻松将用于处理错误的代码移动到非本地点(呼叫更改的较高位置),并知道如何在给定更广泛的上下文的情况下处理问题。 – 2008-12-10 05:12:33

10

异常处理很有用,因为它可以很容易地将错误处理代码与编写用于处理程序功能的代码分开。这使得阅读和编写代码变得更容易。

7
  • 返回时发生错误情况预计在某些情况下
  • 抛出一个异常时发生错误情况预计在任何情况下
在前者的情况下

的错误代码函数的调用者必须检查错误代码是否有预期的失败;在后一种情况下,例外情况可由任何呼叫者在栈上(或默认处理程序)处理(如适用)

17

好处是,您不必在每次潜在呼叫失败后检查错误代码。为了让这个工作起来,你需要将它与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 /例外版本似乎更长,直到你意识到,仅需要编写清理代码一次(并且有方法可以简化)。但DoSomeThings的第二个版本更加清晰和可维护。

不要试图在C++中使用异常而不使用RAII语言,因为会泄漏资源和内存。所有的清理都需要在堆栈分配对象的析构函数中完成。

我意识到还有其他的方式来做错误代码处理,但他们都看起来有点相同。如果你放弃goto,你最终会重复清理代码。

错误代码的一个要点是,它们明确了事情可能发生故障的位置以及它们如何失败。在上面的代码中,你假设事情不会失败(但是如果他们这样做,你会受到RAII包装的保护)。但是你最终不太理会事情会出错的地方。

+0

您也可以结合RAII和常规错误代码。 return语句在某种程度上等同于throw,因为它们都会调用范围内的堆栈对象的dtors。所以不需要转到。 – QBziZ 2008-10-13 09:46:52

+0

好点。在我的脑海里,错误代码总是用于C,所以我的大脑并没有飞跃。 – Eclipse 2008-10-13 15:30:30

2

Here's EAFP的一个很好的解释(“容易问宽恕比权限。”),我认为这适用于这里,即使它是维基百科的Python页面。使用例外会导致更自然的编码风格,海事组织 - 以及其他许多人的看法。

8

除了提到的其他内容,您不能从构造函数返回错误代码。也可以使用析构函数,但是也应该避免从析构函数中抛出异常。

0

正如@马丁指出抛出异常迫使程序员处理错误。例如,不检查返回代码是C程序中最大的安全漏洞来源之一。例外情况确保您处理错误(希望),并为您的程序提供某种恢复路径。如果您选择忽略异常而不是引入安全漏洞,那么您的程序将崩溃。

20

例外的优势有两个方面:

  • 它们不能被忽略。你必须在某个级别处理它们,否则他们会终止你的程序。有了错误代码,您必须明确检查它们,否则它们会丢失。

  • 它们可以被忽略。如果一个错误无法在一个层次上处理,它会自动在其可能的位置上升到下一个层次。错误代码必须显式传递,直到达到可以处理的级别。

+0

这应该是IMO的最佳答案! – Gob00st 2012-01-10 15:09:53

+0

可否请您详细说明第二点(也许有代码示例),我不太了解它 - 谢谢。 – Xsmael 2017-03-23 18:09:25

+1

@Xsmael假设FuncA()调用FuncB(),它调用调用FuncD()的FuncC()。 FuncD()可以引发异常,如果FuncB或FuncC中没有捕获,它可以在FuncA()中捕获。使用错误代码,FuncC必须接受来自FuncD的代码,并将其返回给FuncB。 – 2017-03-24 07:52:57

1

有时你真的必须使用异常来标记异常情况。例如,如果在构造函数中出现错误,并且发现通知调用者这是有意义的,那么您别无选择,只能抛出异常。

又如:有时候没有价值你的函数可以返回来表示一个错误;函数可能返回的任何值都表示成功。

int divide(int a, int b) 
{ 
    if(b == 0) 
     // then what? no integer can be used for an error flag! 
    else 
     return a/b; 
} 
2

当我过去教C++时,我们的标准解释是,他们允许您避免纠结晴天和雨天的情况。换句话说,你可以编写一个函数,就好像一切都可以正常工作,并在最后捕获异常。

没有异常,你就一定得从每个调用的返回值,并确保它仍然是合法的。

当然,一个相关的好处是你不会“浪费”你的异常返回值(因此允许方法应该是无效的),并且还可以从构造函数和析构函数中返回错误。

4

我写这个博客条目(Exceptions make for Elegant Code),随后发表在Overload。我实际上是为了回应Joel在StackOverflow播客中所说的话而编写的!

无论如何,我坚信,例外是最好错误在大多数情况下代码。我发现使用返回错误代码的函数真的很痛苦:每次调用后都必须检查错误代码,这可能会中断调用代码的流程。这也意味着你不能使用重载操作符,因为没有办法指示错误。

检查错误代码的痛苦意味着人们经常忽视这样做,因此使它们完全没有意义:至少你必须明确地忽略与catch声明的异常。

使用在C++析构函数和处置者在.NET,以确保资源在例外也可以极大地简化代码的存在正确释放。为了获得与错误代码相同的保护级别,您可能需要大量的if语句,大量重复的清理代码或goto调用在函数结束时的公用清理块。这些选项都不令人愉快。

1

事实上,你必须承认异常是正确的,但这也可以使用错误结构来实现。 您可以创建一个基本错误类,用于检查其dtor中是否调用了某个方法(例如IsOk)。如果没有,你可以登录一些东西,然后退出,或抛出异常,或提出断言等...

只要调用错误对象上的IsOk而不作出反应,那么将等同于写入catch (...){} 这两个陈述都会显示出同样缺乏程序员的善意。

将错误代码传输到正确的级别是一个更大的问题。基本上,为了传播的原因,几乎所有的方法都会返回一个错误代码。 但是,然后再次,一个函数或方法应该总是注释它可以生成的例外。所以基本上你必须有同样的问题,没有一个接口来支持它。

2

Google's C++ Style Guide对C++代码中异常使用的优缺点有很好的深入分析。这也表明你应该提出一些更大的问题;即我是否打算将我的代码分发给其他人(可能难以集成启用异常的代码库)?

相关问题