19

在另一个问题中,有人建议使用块catch(...)来捕获所有未处理的 - 意外/不可预见的异常。main()try{}catch(...){}

这听起来像是一个有趣的想法,可以节省大量调试程序的时间,并至少留下一点线索。

问题的本质是可以通过这种方式恢复哪些信息(除了我留下的任何调试全局变量),以及如何恢复它(如何访问和识别调用的任何 catch)

此外,还有哪些注意事项与之相关。尤其是:

  • 它会与以后发芽的线程一起玩吗?
  • 它不会破坏处理段错误(在其他地方作为信号捕获)
  • 它不会影响不可避免地嵌套在其中的其他 try...catch 块,它们可以处理预期的异常吗?
4

6 回答 6

15

是的,这是个好主意。

如果你让一个异常逃逸 main 它是实现定义的天气堆栈在应用程序关闭之前展开。所以在我看来,你必须在 main 中捕获所有异常。

那么问题就变成了如何处理它们。
某些操作系统(请参阅 MS 和 SE)提供了一些额外的调试工具,因此在捕获异常后重新抛出异常很有用(因为无论如何现在堆栈已经展开)。

int main()
{
    try
    {
        /// All real code
    }
    // I see little point in catching other exceptions at this point 
    // (apart from better logging maybe). If the exception could have been caught
    // and fixed you should have done it before here.

    catch(std::exception const& e)
    {
         // Log e.what() Slightly better error message than ...
         throw;
    }
    catch(...)   // Catch all exceptions. Force the stack to unwind correctly.
    {
        // You may want to log something it seems polite.
        throw;  // Re-throw the exception so OS gives you a debug opportunity.
    }
}
  • 它会与以后发芽的线程一起玩吗?

它应该对线程没有影响。通常您必须手动加入任何子线程以确保它们已退出。没有很好地定义主退出时子线程会发生什么的确切细节(因此请阅读您的文档),但通常所有子线程都会立即死亡(不涉及展开其堆栈的令人讨厌和可怕的死亡)。

如果您在谈论子线程中的异常。同样,这没有很好的定义(因此请阅读您的文档),但是如果线程通过异常退出(即用于启动线程的函数由于异常而不是返回而退出),那么这通常会导致应用程序终止(同样的影响如上)。所以最好总是阻止所有异常退出线程。

  • 它不会破坏处理段错误(在其他地方作为信号捕获)

信号不受异常处理机制的影响。
但是因为信号处理程序可能会在堆栈上放置一个奇怪的结构(为了他们自己的返回处理返回到正常代码),所以从信号处理程序中抛出异常不是一个好主意,因为这可能会导致意外结果(并且绝对不可移植)。

  • 它不会影响不可避免地嵌套在其中的其他 try...catch 块,它们可以处理预期的异常吗?

应该对其他处理程序没有影响。

于 2010-02-02T15:02:03.163 回答
5

据我记得,catch(...)在 Win32 上也会捕获 SEH 异常,而您不想这样做。如果你得到一个 SEH 异常,那是因为发生了一些非常可怕的事情(主要是访问冲突),所以你不能再信任你的环境了。几乎你能做的所有事情都可能因另一个 SEH 异常而失败,所以它甚至不值得尝试。此外,一些 SEH 异常旨在被系统捕获;更多关于这里

因此,我的建议是为所有异常使用基本异常类(例如std::exception),并在“catchall”中捕获该类型;您的代码无法准备好处理其他类型的异常,因为它们在定义上是未知的。

于 2010-02-02T12:33:21.307 回答
4

全局 try catch 块对于生产系统很有用,以避免向用户显示讨厌的消息。在开发过程中,我认为最好避免这种情况。

关于你的问题:

  • 我相信全局 catch 块不会在另一个线程中捕获异常。每个线程都有自己的堆栈空间。
  • 我不确定这一点。
  • 嵌套的 try...catch 块不受影响,将照常执行。异常向上传播堆栈,直到找到一个 try 块。
于 2010-02-02T10:01:08.753 回答
2

如果您正在制作 .net 应用程序,您可以尝试我使用的解决方案。这捕获了所有未处理的异常。#ifndef DEBUG当我不使用调试器时,我通常只为生产代码启用代码(带有)。

值得指出的是,kgiannakakis 提到您无法在其他线程中捕获异常,但您可以在这些线程中使用相同的 try-catch 方案并将异常发布回主线程,您可以重新抛出它们以获得完整的堆栈跟踪出了什么问题。

于 2010-02-02T10:02:12.913 回答
1

以及如何恢复它(如何访问和识别调用的任何捕获)

如果您的意思是如何恢复抛出的异常类型,您可以在回退到之前链接特定类型的 catch 块(从更具体到更一般)catch (...)

try {
   ...
} catch (const SomeCustomException& e) {
   ...
} catch (const std::bad_alloc& e) {
   ...
} catch (const std::runtime_error& e) {
   // Show some diagnosic for generic runtime errors...
} catch (const std::exception& e) {
   // Show some diagnosic for any other unhandled std::exceptions...
} catch (...) {
   // Fallback for unknown errors.
   // Possibly rethrow or omit this if you think the OS can do something with it.
}

请注意,如果您发现自己在多个地方执行此操作并希望合并代码(可能main是单独程序的多个函数),您可以编写一个函数:

void MyExceptionHandler() {
   try {
      throw; // Rethrow the last exception.
   } catch (const SomeCustomException& e) {
      ...
   }
   ...
}

int main(int argc, char** argv) {
   try {
      ...
   } catch (...) {
      MyExceptionHandler();
   }
}
于 2010-02-02T10:17:44.783 回答
0

因为没有可以查询的类型/对象信息,所以包罗万象不会非常有用。但是,如果您可以确保应用程序引发的所有异常都派生自单个基础对象,则可以对基础异常使用 catch 块。但这不会是万能的。

于 2010-02-02T10:22:24.747 回答