你(真的)编写异常安全代码吗?

时间:2009-12-05 19:48:16

标签: c++ exception-handling

异常处理(EH)似乎是当前的标准,通过搜索网络,我找不到任何试图改进或替换它的新颖想法或方法(好吧,存在一些变化,但没有新颖的。) / p>

虽然大多数人似乎忽略它或只是接受它,但是EH 具有一些巨大的缺点:代码看不到异常,它会创建许多可能的退出点。乔尔在软件上写了article about it。与goto的比较非常合适,它让我再次想到了EH。

我尽量避免使用EH,只使用返回值,回调或任何适合目的的东西。但当你必须编写可靠的代码时,你现在就不能忽视EH :它以new开头,它可能抛出一个异常,而不是只返回0(如过去的日子)。这使得任何C ++代码行易受攻击成为异常。然后C ++基础代码中的更多地方会抛出异常...... std lib会执行它,依此类推。

感觉就像在不稳定的场地上行走 ..所以,现在我们被迫关注异常!

但它很难,真的很难。你必须学会​​编写异常安全代码,即使你有一些经验,它仍然需要仔细检查任何一行代码是安全的!或者你开始在任何地方放置try / catch块,这会使代码混乱,直到它达到不可读状态。

<\ n> EH取代了旧的干净确定性方法(返回值..),这种方法只有一些但可以理解且容易解决的缺点,在您的代码中创建了许多可能的退出点,并且如果您开始编写捕获异常的代码(你在某些时候被迫做什么),然后它甚至通过你的代码创建了许多路径(catch块中的代码,考虑一个服务器程序,你需要除了std :: cerr之外的日志工具..)。 EH有优势,但这不是重点。

我的实际问题:

  • 你真的写了异常安全代码吗?
  • 您确定最后一次“生产就绪”代码是安全的吗?
  • 你能确定吗,是吗?
  • 你知道和/或实际使用有效的替代品吗?

13 个答案:

答案 0 :(得分:507)

答案 1 :(得分:31)

在C ++中编写异常安全的代码与使用大量try {} catch {}块无关。它是关于记录代码提供什么样的保证。

我建议阅读Herb Sutter的Guru Of The Week系列,特别是分期59,60和61.

总而言之,您可以提供三种级别的异常安全性:

  • 基本:当您的代码抛出异常时,您的代码不会泄漏资源,并且对象仍然是可破坏的。
  • Strong:当您的代码抛出异常时,它会保持应用程序状态不变。
  • 不抛:你的代码永远不会抛出异常。

就个人而言,我发现这些文章的时间很晚,所以我的C ++代码绝对不是例外安全的。

答案 2 :(得分:18)

我们中的一些人已经使用例外超过20年。例如,PL / I有它们。他们是一种新的危险技术的前提似乎对我来说是个问题。

答案 3 :(得分:16)

首先(正如Neil所说),SEH是微软的结构化异常处理。它与C ++中的异常处理类似但不完全相同。实际上,如果你想在Visual Studio中使用它,你必须enable C++ Exception Handling - 默认行为并不能保证在所有情况下都会销毁本地对象!在任何一种情况下,异常处理都不是更难它只是不同

现在提出您的实际问题。

  

你真的写异常安全代码吗?

是。在所有情况下,我都努力寻求异常安全代码。我使用RAII技术宣传资源的范围访问(例如,boost::shared_ptr用于内存,boost::lock_guard用于锁定)。通常,RAIIscope guarding技术的一致使用将使异常安全代码更容易编写。诀窍是了解存在的内容以及如何应用它。

  

您确定最后一次“生产就绪”代码是否异常安全吗?

没有。它既安全又安全。我可以说,由于几年的24/7活动中的异常,我没有看到过程故障。我不希望完美的代码,只是编写良好的代码。除了提供异常安全性之外,上述技术还保证了使用try / catch块几乎无法实现的正确性。如果您正在捕获最高控制范围(线程,进程等)中的所有内容,那么您可以确保在异常(most of the time)面前继续运行。相同的技术还可以帮助您在面对异常的情况下继续正确运行 ,而无需try / catch阻止

  

你能确定它是吗?

是。您可以通过彻底的代码审核来确定,但没有人真正这样做吗?定期的代码审查和细心的开发人员可以在很长的路要走。但

  

您知道和/或实际使用有效的替代品吗?

多年来我尝试了一些变体,例如在高位编码状态(ala HRESULTs)或可怕的setjmp() ... longjmp()黑客。这两种方式在实践中都以完全不同的方式分解。


最后,如果您习惯于应用一些技巧并仔细考虑在异常情况下实际可以执行某些操作的位置,那么最终会得到非常易读且异常安全的代码。您可以按照以下规则进行总结:

  • 您只想在可以对特定例外采取行动时看到try / catch
  • 您几乎不想在代码中看到原始的newdelete
  • Eschew std::sprintfsnprintf和一般数组 - 使用std::ostringstream进行格式设置并将数组替换为std::vectorstd::string
  • 如有疑问,请在推出自己的
  • 之前在Boost或STL中查找功能

我只能建议您学习如何正确使用异常,如果计划用C ++编写,请忘记结果代码。如果您想避免例外,您可能需要考虑使用does not have themmakes them safe的其他语言编写。如果您想真正学习如何充分利用C ++,请阅读Herb SutterNicolai JosuttisScott Meyers中的几本书。

答案 4 :(得分:9)

在“任何行可以抛出”的假设下,不可能编写异常安全的代码。异常安全代码的设计主要依赖于您应该在代码中期望,观察,遵循和实现的某些合同/保证。绝对有必要保证永远不会抛出的代码。还有其他种类的例外保证。

换句话说,创建异常安全代码在很大程度上是程序设计的问题,而不仅仅是普通的编码

答案 5 :(得分:7)

  • 你真的写了异常安全代码吗?

嗯,我当然打算。

  • 您确定最后一次“生产就绪”代码是安全的吗?

我确信使用异常构建的24/7服务器每天24小时运行并且不会泄漏内存。

  • 你能确定吗,是吗?

很难确定任何代码是否正确。通常,人们只能按结果

  • 你知道和/或实际使用有效的替代品吗?

没有。使用例外比过去30年在编程中使用的任何替代方案更清晰,更容易。

答案 6 :(得分:5)

除了SEH和C ++异常之间的混淆之外,您需要注意可以随时抛出异常,并在编写代码时考虑到这一点。对异常安全的需求在很大程度上推动了RAII,智能指针和其他现代C ++技术的使用。

如果遵循完善的模式,编写异常安全的代码并不是特别困难,事实上它比编写在所有情况下都能正确处理错误返回的代码更容易。

答案 7 :(得分:4)

一般来说,EH很好。但是C ++的实现并不是非常友好,因为很难说你的异常捕获覆盖有多好。例如,Java使这很容易,如果你不处理可能的异常,编译器往往会失败。

答案 8 :(得分:2)

我真的很喜欢使用Eclipse和Java(Java新手),因为如果你缺少一个EH处理程序,它会在编辑器中抛出错误。这使得忘记处理异常变得更加困难......

另外,使用IDE工具,它会自动添加try / catch块或其他catch块。

答案 9 :(得分:2)

我们中的一些人更喜欢像Java这样的语言,它迫使我们声明方法抛出的所有异常,而不是像C ++和C#那样使它们不可见。

如果正确完成,异常优于错误返回代码,如果没有其他原因,您不必手动将失败传播到调用链。

话虽这么说,低级API库编程应该可以避免异常处理,并坚持使用错误返回码。

根据我的经验,用C ++编写干净的异常处理代码很困难。我最终使用了new(nothrow)

答案 10 :(得分:2)

我尝试最好的编写异常安全的代码,是的。

这意味着我要注意留下哪些行可以抛出。不是每个人都可以,并且牢记这一点至关重要。关键是要考虑并设计您的代码以满足标准中定义的异常保证。

是否可以编写此操作以提供强大的异常保证?我必须满足于基本的吗?哪些行可能会抛出异常,如何确保它们不会破坏对象?

答案 11 :(得分:2)

  • 你真的写异常安全代码吗? [没有这样的事情。除非您拥有托管环境,否则例外是对错误的纸张屏障。这适用于前三个问题。]

  • 您知道和/或实际使用有效的替代品吗? [替代什么?这里的问题是人们不会将实际错误与正常程序操作分开。如果是正常的程序操作(即找不到文件),则不是真正的错误处理。如果是实际错误,则无法“处理”它或者它不是实际错误。你的目标是找出问题所在并停止电子表格并记录错误,将驱动程序重新启动到烤面包机,或者只是祈祷喷气式战斗机可以继续飞行,即使它的软件是错误的并希望最好。] < / p>

答案 12 :(得分:0)

人们做了很多(我甚至会说最多)。

异常真正重要的是,如果你不编写任何处理代码 - 结果是非常安全和良好的。太急于恐慌,但很安全。

你需要主动在处理程序中犯错才能获得不安全的东西,只有catch(...){}会与忽略错误代码进行比较。