【问题标题】:When and how should I use exception handling?我应该何时以及如何使用异常处理?
【发布时间】:2011-05-29 05:28:10
【问题描述】:

我正在阅读有关异常处理的信息。我得到了一些关于什么是异常处理的信息,但我有几个问题:

  1. 什么时候抛出异常?
  2. 我们可以使用返回值来指示错误,而不是抛出异常吗?
  3. 如果我通过 try-catch 块保护我的所有函数,会不会降低性能?
  4. 何时使用异常处理?
  5. 我看到一个项目,该项目中的每个函数都包含一个 try-catch 块(即整个函数内的代码都被 try-catch 块包围)。这是一个好习惯吗?
  6. try-catch 和 __try __except 有什么区别?

【问题讨论】:

标签: c++ windows exception exception-handling


【解决方案1】:

如果您的问题来自您自己的错误代码,最好使用 ASSERT 来防范它。需要异常处理来识别程序无法处理的问题并告诉他们有关用户的信息,因为用户可以处理它们。但是程序中的错误不是用户可以处理的,因此程序崩溃不会说明太多

我不同意the accepted answer 的这一方面。断言并不比抛出异常更好。如果异常只适用于运行时错误(或“外部问题”),那么std::logic_error 是干什么用的?

逻辑错误几乎是一种阻止程序继续运行的条件。如果程序是一个逻辑结构,并且在该逻辑域之外发生了一个条件,它怎么能继续呢?尽可能收集你的输入,然后抛出异常!

这并不是没有现有技术。 std::vector,仅举一个例子,抛出一个逻辑错误异常,即std::out_of_range。如果您使用标准库并且没有顶级处理程序来捕获标准异常——如果只是调用 what() 和 exit(3)——那么您的程序会突然无声,终止。

断言宏是一个弱得多的保护。没有恢复。除非,也就是说,您没有运行调试版本,在这种情况下,没有执行。 assert 宏属于计算比现在慢 6 个数量级的时代。如果您要麻烦地测试逻辑错误,但在需要时不使用该测试,那么在生产中,您最好对您的代码充满信心!

标准库提供逻辑错误异常,并使用它们。它们存在是有原因的:因为发生逻辑错误并且是异常的。仅仅因为 C 具有断言功能,就没有理由依赖这种原始(并且可以说是无用的)机制,因为异常可以更好地处理工作。

【讨论】:

    【解决方案2】:

    这是我认为必须阅读的关于异常的非常全面的指南:

    异常和错误处理 - C++ FAQC++ FAQ lite

    作为一般经验法则,当您的程序可以识别出阻止执行的外部问题时,抛出异常。如果您从服务器接收数据并且该数据无效,则抛出异常。磁盘空间不足?抛出异常。宇宙射线阻止您查询数据库?抛出异常。但是如果你从你自己的程序中得到一些无效数据——不要抛出异常。如果您的问题来自您自己的错误代码,那么最好使用 ASSERT 来防范它。需要异常处理来识别程序无法处理的问题并告诉他们有关用户的信息,因为用户可以处理它们。但是程序中的错误不是用户可以处理的,因此程序崩溃所显示的信息不会比“answer_to_life_and_universe_and_everything 的值不是 42!这绝不应该发生!!!!11”异常。

    捕获一个异常,你可以用它做一些有用的事情,比如显示一个消息框。我更喜欢在以某种方式处理用户输入的函数中捕获异常。例如,用户按下按钮“Annihilate all hunams”,并且在 annihilateAllHunamsClicked() 函数中有一个 try...catch 块来表示“我不能”。尽管消灭人类是一个复杂的操作,需要调用几十个函数,但只有一次尝试...捕获,因为对于用户来说,这是一个原子操作——一键单击。每个函数中的异常检查都是多余且丑陋的。

    另外,我不建议您充分熟悉 RAII - 即确保所有已初始化的数据都被自动销毁。这可以通过在堆栈上尽可能多地初始化来实现,当您需要在堆上初始化某些东西时,请使用某种智能指针。当抛出异常时,在堆栈上初始化的所有内容都将自动销毁。如果你使用 C 风格的哑指针,当抛出异常时你会冒内存泄漏的风险,因为没有人在出现异常时清理它们(当然,你可以使用 C 风格的指针作为你的类的成员,但要确保它们是在析构函数中处理)。

    【讨论】:

    • 感谢您的宝贵意见。 >“需要异常处理来识别程序的问题 >无法处理并告诉他们有关用户的信息,因为用户可以 >处理它们”我可以返回错误值并在调用函数中检查错误,而不是抛出异常并显示一条消息,帮助用户处理它。
    • 再次查看FAQ,尤其是本节中的第三和第四个代码sn-ps:parashift.com/c++-faq-lite/exceptions.html#faq-17.2 异常很棒,因为它们会让您的错误从调用堆栈的任何深度浮出水面。 .. 就像错误代码是深海球,例外是潜艇:)
    • @Septagram “如果你的问题来自你自己的错误代码,最好使用 ASSERT 来防范它。”:断言不会让你继续执行不依赖于错误分支(仅仅因为您的程序在一个函数中有错误并不意味着它不能做其他有用的事情)。它不会让您使用自定义记录器。它将绕过您通过使用 RAII 受益的析构函数(希望您的文件缓冲区在程序终止之前被刷新?最好不要使用 fclose 跳过那些析构函数!)。它不会为您提供完整的堆栈跟踪。
    • 关于 RAII 和“哑指针”的一些话。仅仅因为您使用“哑指针”并不意味着您没有遵循 RAII。请记住,只有在分配系统资源时才需要确保它们被释放。指针只是堆栈上保存内存地址的变量。使用“哑指针”不需要对象分配和初始化。 “哑指针”可以简单地保存分配在别处的对象的地址,并且完全可以使用。 (在下一条评论中继续)
    • 您可能有一个 Renderer 对象,该对象将 Display 对象的地址存储在指针中,以便将内容绘制到 Display,但您不能使用智能指针,因为智能指针会破坏销毁 Renderer 对象时显示对象,这是您不希望发生的。在这种情况下,“哑指针”或引用是唯一的方法。
    【解决方案3】:

    基本区别是:

    1. 为您进行错误处理。
    2. 一个是你自己做的。

      • 例如,您有一个表达式可以使0 divide error。使用 try catch 1. 将在发生错误时为您提供帮助。或者你需要if a==0 then..2.

      • 如果您不尝试捕获异常,我认为它不会更快,它只是简单地绕过,如果发生error,它将是threw 到外部处理程序。

    交给自己意味着问题不会更进一步,在很多情况下会在速度上占优势,但并非总是如此。

    建议:在简单且合乎逻辑的情况下处理自己。

    【讨论】:

      【解决方案4】:

      最好读一下

      在过去的 15 年里,异常处理已经被讨论了很多。然而,尽管就如何正确处理异常达成了普遍共识,但在使用上的分歧仍然存在。不正确的异常处理很容易发现,很容易避免,并且是一个简单的代码(和开发人员)质量指标。我知道绝对规则会显得过于狭隘或夸大其词,但作为一般规则,您不应该使用 try/catch

      http://codebetter.com/karlseguin/2010/01/25/don-t-use-try-catch/

      【讨论】:

      • 那篇文章不是真的想说,不要用try/catch {}吗?
      【解决方案5】:

      异常在各种情况下都很有用。

      首先,有些函数计算前置条件的成本非常高,如果发现前置条件不满足,最好只进行计算并异常中止。例如,您不能反转奇异矩阵,但是要计算它是否是奇异矩阵,您需要计算非常昂贵的行列式:无论如何它可能必须在函数内部完成,因此只需“尝试”反转矩阵并报告如果你不能通过抛出异常来报错。这基本上是一个作为否定前提条件的例外

      在其他情况下,您的代码已经很复杂,很难将错误信息传递到调用链上。这部分是因为 C 和 C++ 破坏了数据结构模型:还有其他更好的方法,但 C++ 不支持它们(例如在 Haskell 中使用 monads)。这种用法基本上是我懒得做对了,所以我会抛出一个异常:它不是正确的方法,但它很实用。

      然后是异常的主要用途:在外部前提条件或不变量(例如内存或磁盘空间等足够的资源)不可用时报告。在这种情况下,您通常会终止程序或程序的主要部分,而例外是传输有关问题的信息的好方法。 C++ 异常旨在报告阻止程序继续运行的错误

      在包括 C++ 在内的大多数现代语言中使用的异常处理模型已知会被破坏。它太强大了。理论家现在已经开发出比完全开放的“扔任何东西”和“也许抓到它”模型更好的模型。此外,使用类型信息对异常进行分类也不是一个好主意。

      所以你能做的最好的事情是谨慎地抛出异常,当存在实际错误并且没有其他方法来处理它时在接近抛出点时捕获异常尽可能

      【讨论】:

      • 能否为“已知异常处理已损坏”和“理论家现在开发出更好的模型”添加一些链接/参考?
      • 捕获异常时经常需要知道的一个关键问题是,抛出异常的代码是否对系统状态有任何影响,但大多数异常并没有提供有用的数据。任何理论家的模型都处理这个问题吗?
      • @JurajBlaho 我试图找到您正在谈论的链接/参考但没有成功。您可以编辑他的答案以在最后添加它们吗?
      • 我同意示例 #1 和 #3。但是,我确实认为,如果代码太复杂,那么您考虑抛出异常可能需要进行一些重构。断言也是检查代码是否正常运行的好方法(甚至更好),它实际上比异常更便宜。我向任何对此主题感兴趣的人推荐本书中的 2 章(断言编程和何时使用异常):实用程序员。
      • 不需要链接。使用大脑。您可以抛出未捕获的异常。那很糟。比 goto 更糟糕,因为 goto 至少总是去某个地方。静态类型无法处理异常规范:没有健全的系统可以让多态函数具有异常规范,因为它依赖于类型变量的特定实例。请注意这些已从新的 C++ 标准中删除。新模型称为“定界延续”:en.wikipedia.org/wiki/Delimited_continuation
      【解决方案6】:

      许多 C/C++ 纯粹主义者完全不鼓励异常。主要批评有:

      1. 它很慢 - 当然它并不是真的“慢”。不过和纯c/c++相比,开销还是蛮大的。
      2. 它引入了错误 - 如果您没有正确处理异常,您可能会错过引发异常的函数中的清理代码。

      相反,每次调用函数时都要检查返回值/错误代码。

      【讨论】:

      • 废话。首先,我们谈论的是 C++——没有“C/C++”,当然只有 C 语言的程序员会对异常感到不舒服。 “纯 C/C++”不存在,异常是“纯 C++”的一部分——它们在标准中。您可以编写有缺陷的异常处理代码,也可以编写有缺陷的错误返回代码或有缺陷的错误状态代码——将异常描述为通常更容易出错是一种误导。抱歉,这是我在 S.O. 上看到的最糟糕的建议
      • 1.只有在实际抛出异常时它才会很慢。而异常之所以称为异常,是因为只有在异常情况下才会抛出。每秒抛出超过 9000 个异常的程序是错误的。 2.如果你注意不要用纯new-delete做内存管理,清理会为你完成。 C 风格的内存管理比异常更容易出错。 3.检查返回值会增加很多额外的代码行,从而使其可读性和可维护性降低。
      • 1) 取决于可能不正确的编译器。并且所有异常都会添加大量字节码,从而增加可执行文件的大小并减慢速度。 2)一直使用纯new-delete,并不是所有的东西都可以是堆栈对象。 3)检查返回值可能需要更多的代码行,但可以说它更易于维护。您可能不同意,但普遍接受的合理信念是 C++ 异常在许多情况下是不好的。以 Embedded-C++ 为例。
      • 11 票反对和 8 票赞成。我认为很明显,虽然这种观点并未反映普遍共识,但由于我所描述的原因,有大量开发人员对例外情况持谨慎态度。
      【解决方案7】:

      1.当有可能导致异常或介于问题之间的某个地方时,代码中会包含异常检查。

      2. 仅在需要的情况下使用 try-catch 块。每个 try-catch 块的使用都会增加额外的条件检查,这肯定会降低代码的优化。

      3.我认为 _try_except 是一个有效的变量名......

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-01
      • 1970-01-01
      • 2016-08-04
      • 2011-06-09
      • 1970-01-01
      • 2015-04-02
      相关资源
      最近更新 更多