在C ++中使用异常的可能的错误处理策略是什么,它们的后果和含义是什么?

时间:2019-02-20 09:56:47

标签: c++ exception-handling

我希望您能帮助您了解在C ++中使用/禁用异常的可能方法。

我的问题不是关于什么是最佳选择,而是关于什么是可能的选择以及这些选择意味着什么。

目前,我能想到的选择是:

  1. 使用-fno-exceptions进行编译并放弃大多数std容器(可能定义了不抛出的内部容器,例如SpiderMonkey Coding_Style中的建议)
  2. 只是避免引发和捕获自己的代码,但仍使用可能引发异常的std容器。对以下情况感到满意:在例外情况下,程序可能会终止而不会取消堆栈,甚至RAII处理的外部资源也可能会挂起。 (根据对this SO question的回答,这似乎是Google C ++的方法)
  3. 不使用异常,而是将所有内容包装在catch std :: exception try块中,以确保在程序终止之前确保堆栈未展开并且RAII句柄已释放,例如this Cert C++ rule
  4. 如上所述,但还会引发异常,最终将导致程序终止。
  5. 还使用捕获的异常并从异常中恢复。

我想知道我对选项的理解是否正确,以及我可能会漏掉哪些东西或理解错了。

我还想知道授予选项2至4的基本例外安全授予约束是否有意义(其中例外总是最终导致程序终止),或者是否/如何将例外安全要求放宽/限制为特定情况(例如,处理外部资源,文件)。

1 个答案:

答案 0 :(得分:3)

更新

严格来说,当您想禁用异常时,只有不带异常支持的编译才是真正的方法,所以选择1。因为当您想要禁用异常时,您也不应该使用甚至处理它们。引发异常将立即终止或在大多数实现上陷入严重错误,只有这样,您才能避免开销问题,例如空间或性能开销,即使它们很小(参见下文)。

但是,如果您只是想知道关于异常用法的范式,那么您的选择几乎是正确的,您没有提到一般性的想法,那就是保持异常异常并做可能或什至可能抛出的事情,在程序启动时。

更一般而言,通常涉及错误处理:异常可以处理您在运行时遇到的错误,如果正确完成,则只能在运行时检测到错误。您可以通过简单地确保解决运行时之前可以检测到的所有问题来避免异常,例如正确的编码和检查(编码时间),使用严格的类型和检查(模板,编译时间),然后再次检查它们(静态分析器)。 / p>

更新2

如果您对关心异常安全的理解是错误的,我会说: 基本上,起初,通常取决于您何时启用异常:如果禁用了异常,则您将不会也不应在意异常的安全性,因为没有异常(或者如果它们尝试存在,则无论如何都会终止/崩溃/硬故障)。 如果启用了异常但未在代码中使用它们(如情况2、4和3),则无论如何都不会出现问题,因此丢失的清除代码无关紧要(而且3.中的内容仍然可以在任何情况下运行)案件)。但是,然后应该向所有人明确表示您不想使用它们,这样他们就不会尝试从异常中恢复。 而且,如果您在代码中使用它们,则应该以某种方式关心异常安全性,即当引发异常时,您也要在自我之后进行清理,然后清理它的主处理程序或将来的代码更改(无论何时)终止或可以恢复。不清理但仍然使用异常是没有意义的。然后,您可以坚持使用选项1。

据我所知,这应该是详尽无遗的。有关更多信息,请参见下文。

推荐

我建议使用选项4,然后告诉您原因:

异常是一种非常容易理解和滥用的模式。它们被滥用为程序流,就像Java过度使用的语言一样。 C ++ std基本上说“实现魔术”。由于它们的本质很难区分哪个处理程序将捕获该处理程序,是否存在该处理程序以及将花费多长时间,因此通常在嵌入式或安全代码中禁止使用它们。 / p>

背景

但是,在我的推理中,对异常的讨厌基本上是一个大的XY问题: 当人们抱怨自己的恢复能力受损并且很难说时,那么通常的问题就是看不见你不能或不应该对大多数例外做很多事情,这就是他们的目的。但是,超时或关闭tcp连接之类的事情几乎不是不正常的,但是许多人为此使用异常,这当然是错误的。但是,如果整个操作系统都告诉您没有网络适配器或内存不足,您该怎么办?您可能想要的唯一一件事就是尝试在某处记录原因,并且应该在主块周围进行一次try / catch来完成。

对于安全/实时程序也是如此:以内存不足的例子为例,当发生这种情况时,无论如何,您的策略就是在不安全的初始化时间执行此操作。例外也不是问题。

在使用带有抛出成员的容器的情况类似的情况下,当获得错误代码时您将能做什么?不多,这就是为什么您要在代码时确保没有错误的原因,例如确保元素确实在其中,或为向量保留容量。

这具有更清晰的代码的好处,不必忘记检查错误且不会降低性能,至少使用通用的C ++实现(例如gcc等)即可。

3的原因值得商de,我认为您也可以这样做,但是我的问题是:您必须在析构函数中执行什么操作,而这些析构函数还是无法清理您的操作系统?操作系统将清理内存,套接字等。如果有独立的方案,那么问题就一直存在,只要不一样:例如,您计划由于UART中断而暂停,在暂停之前您想做什么?为什么在重新投掷之前不能在catch块中做到这一点?

总结

  1. 存在不使用任何引发代码的问题,并且仍然存在如何处理罕见错误代码的问题。 (这就是为什么如此多的C程序员仍然使用goto或跳远的原因)

  2. 恕我直言,两者都不可行

  3. 如所提到的好,但是您需要在静态DTor中做什么,甚至非正常终止也不会做什么?

  4. 我的最爱

  5. 仅当您确实有极少数情况可以恢复时才可以

我的一般建议是:引发和异常应表示发生了某些事情,这些事情在正常运行中永远不会发生,而是由于不可预见的错误而发生,例如硬件故障,电缆分离,找不到共享库,您已完成的编程错误,例如尝试在容器中未包含的索引处使用.at(),而容器却没有。那么只有几乎每次都导致程序终止/硬故障的情况下抛出异常才是合乎逻辑的

使用例外支持进行编译的结果是,例如,对于独立的ARM程序,您的程序大小将增加27kB,对于托管(Linux / Windows等)来说,程序大小将增加27kB,对于独立的情况,程序大小将增加2kB RAM(参见C++ exception handler on gnu arm cortex m4 with freertos

但是,当使用普通的编译器(如clang或gcc),代码正常运行时(即引发异常的分支/ ifs未被触发)时,使用异常不会对性能造成任何影响。

作为参考,即对我的陈述的证明,我指的是ISO/IEC TR 18015:2006 Technical Report on C++ Performance,摘录自7.2.2.3:

  

仅在以下情况下才对实时关键程序启用异常处理:   实际使用异常。完整的分析必须始终包括   引发异常,这种分析将始终是   取决于实现。另一方面,要求采取行动   在确定的时间内发生异常时可能会松动   (例如,当出现以下情况时,无需处理来自设备的任何其他输入   连接已断开)。异常替代方案概述   第5.4节中给出了处理方法。但是如图所示,所有选项都有   运行时成本和引发异常可能仍然是最好的方法   处理特殊情况。只要没有异常抛出   很长的路要走(即,   throw-expression和处理程序),甚至可以减少运行时间   费用。

相关问题