C++捕获所有异常

330

有没有C++的等价物来替代Java的

try {
    ...
}
catch (Throwable t) {
    ...
}

我正在尝试调试调用本地Windows函数的Java/jni代码,但虚拟机一直崩溃。本地代码在单元测试中看起来很好,只有通过jni调用时才会崩溃。一个通用的异常捕获机制将非常有用。


1
如何构建一个C++ DLL包装器来捕获所有异常? - lsalamon
4
请注意,大多数崩溃不是由C ++中的异常引起的。您可以捕获所有异常,但这并不能防止许多崩溃发生。 - Mooing Duck
如果您来到这里,您可能正在寻找以下内容:https://dev59.com/z2kv5IYBdhLWcg3w_lzE#32799720 https://www.man7.org/linux/man-pages/man2/sigaction.2.html https://www.man7.org/linux/man-pages/man7/signal.7.html - Andrew
16个回答

399
try{
    // ...
} catch (...) {
    // ...
}

可以捕获所有C++异常,但这应该被视为不好的设计。您可以使用C++11的new current_exception机制,但如果您没有使用C++11的能力(需要重写的旧代码系统),则没有命名的异常指针可用于获取消息或名称。您可能希望添加单独的catch子句来捕获各种异常,并仅在底部捕获所有异常以记录意外异常。例如:

try{
    // ...
} catch (const std::exception& ex) {
    // ...
} catch (const std::string& ex) {
    // ...
} catch (...) {
    // ...
}

78
最好的做法是通过const引用来捕获异常,示例:catch(std::exception const & ex) { /* ... */ }。 - coryan
16
为什么通过const引用进行捕获是良好的实践? - Tim MB
27
避免不必要的副本是其中一个好处。 - Greg D
31
“这将 '捕获 C++ 中的所有异常' 的建议是误导性的。尝试在 try 块内生成一个除零错误,你会发现它会生成一个未被捕获的异常,但代码显然是在 C++ 中编写的。更有帮助的做法是声明这将 '捕获所有 C++ 异常',并在有关受限用途的说明中添加一些有关结构化异常的提及。” - omatai
48
@omatai: 已更正,现在可以捕获所有的C++异常。零除是未定义的行为,不会产生C++异常。 - Mooing Duck
显示剩余10条评论

181

有人应该补充一下,C++代码中无法捕获“崩溃”(crashes)。这些不会抛出异常,但会执行任何它们想做的事情。当您看到程序因为空指针引用导致崩溃时,这是未定义行为。没有 std::null_pointer_exception。尝试捕获异常也无济于事。

只是万一有人正在阅读本主题并认为自己可以找出程序崩溃的原因,请使用像gdb这样的调试器。


4
正如Shy所指出的那样,使用VC编译器是可能的。虽然这不是一个好主意,但确实是可能的。 - Shog9
7
是的,使用SEH可以做到。但是不能使用标准的C++技术来实现。如果你坚持使用Windows系统,几乎可以做到任何事情。 - Johannes Schaub - litb
1
嗯...谢谢你提供这个小贴士。我一直在寻找答案,为什么我的空指针异常没有被捕获! - user29053
11
在Windows上,你可以使用SEH捕获segmentation faults;在POSIX系统中,你可以使用signal(2)/sigaction(2)。这涵盖了当今大部分使用的系统,但与异常处理一样,它不应用于正常流程控制,而更适用于“在崩溃之前做些有用的事情”。请注意:本翻译力求忠实原意、通俗易懂,没有其他信息返回。 - Adam Rosenfield
1
在实现使用信号/sigaction进行捕获之前,我不会称其为“捕获”:@AdamRosenfield。如果在信号处理程序中,相对于try/catch,程序员很难知道崩溃发生在代码的哪个位置(我说的是通过编程方式检测)。 - Johannes Schaub - litb
显示剩余3条评论

109

如果需要捕获第三方库中的未知异常类型,在 catch(...) 代码块内部,可以使用以下方法反向工程实现异常类型:

#include <iostream>

#include <exception>
#include <typeinfo>
#include <stdexcept>

int main()
{
    try {
        throw ...; // throw something
    }
    catch(...)
    {
        std::exception_ptr p = std::current_exception();
        std::clog <<(p ? p.__cxa_exception_type()->name() : "null") << std::endl;
    }
    return 1;
}

如果你能够使用Boost,你可以使你的异常捕获部分变得更简单(在外部),并且有潜力跨平台。

catch (...)
{
    std::clog << boost::current_exception_diagnostic_information() << std::endl;
}

2
catch(...)catch(std::exception)之间有什么区别? - Mayur
2
在C++中,你可以抛出任何东西,例如一个int...也可以捕获它。顺便说一下,你应该通过const&来捕获异常。 - bobah
FYI,在vs2015中,“boost::current_exception_diagnostic_information()”即使有调试信息也只会返回“无可用诊断信息”。 - Javanator
3
我很确定 p.__cxa_exception_type()->name() 不具有可移植性。实际上,我尝试了使用 clang 进行编译,但它无法通过。 - Edward Falk
在使用catch (...)时,catch语句的主体应该执行throw;以重新抛出异常,例如记录日志,或者终止程序(例如std::terminate())。除非知道如何处理异常,否则不应尝试“处理”它。例如,在某些实现中,尝试终止线程会导致在该线程中引发异常(不是std::exception的子类),因此需要展开线程的堆栈才能停止线程。因此,我们不应“无条件捕获所有异常”,因为可能存在意外情况。 - adentinger
显示剩余3条评论

76
try {
   // ...
} catch (...) {
   // ...
}

请注意,catch块中的...是一个真正的省略号,即三个点。

然而,由于C++异常不一定是基类Exception的子类,因此在使用这种结构时实际上没有办法看到抛出的异常变量。


25
在 C++11 中,有如下代码:try { std::string().at(1); // 这会生成一个 std::out_of_range 异常 } catch(...) { eptr = std::current_exception(); // 捕获异常 } - Mohammad Alaggan
2
@bfontaine:是的,但我这么说是为了将catch说明符与注释中的现有代码占位符(// ...)区分开来,显然这不是C++语法。 - Greg Hewgill
1
@GregHewgill:是的,这只是一些排版细节问题。 - bfontaine
1
@bfontaine:好的,没问题。 :) - Greg Hewgill
那么你的意思是我们不能在(...)中捕获的异常上记录某种e.what(),对吗? - Maf

60

使用 C++,无法以便携的方式捕获所有异常。这是因为某些异常在 C++ 上下文中并不属于异常。这包括诸如除零错误等其他情况。虽然有方法可以绕过这个问题并使其能够在出现这些错误时抛出异常,但这很难做到,并且难以实现便携性。

如果要捕获所有 STL 异常,则可以执行以下操作:

try { ... } catch( const std::exception &e) { ... }

使用e.what()可以返回一个const char*,告诉您有关异常本身的更多信息。这是最像您询问的Java构造的构造。

如果某个人愚蠢到抛出不继承自std::exception的异常,则此方法将无法帮助您。


39

简而言之,使用catch(...)。但是请注意,catch(...)应与throw;结合使用,基本上如下:

try{
    foo = new Foo;
    bar = new Bar;
}
catch(...)       // will catch all possible errors thrown. 
{ 
    delete foo;
    delete bar;
    throw;       // throw the same error again to be handled somewhere else
}

这是使用catch(...)的正确方式。


7
最好使用RAII(资源获取即初始化)来进行内存管理,它可以自动处理这些异常情况。 - hich9n
1
@paykoob,如果您成功创建了一个新的foo但在bar上失败了,该怎么处理呢?或者当bar的构造函数尝试打开文件但失败并因此抛出异常时。那么您可能会遇到悬挂的foo。 - Mellester
2
@MelleSterk 如果是这样,栈仍然会被清理,这将运行Foo的析构函数,不是吗?我认为这就是RAII的全部意义。但是,如果您需要指向Foo的指针而不仅仅是在堆栈上创建Foo,那么您需要将指针包装在其他声明在堆栈上的东西中。 - reirab
是的,auto foo = std::make_unique<Foo>(); auto bar = std::make_unique<Bar>(); // 是异常安全的,不会泄漏,无需 catch(...) - paulm
2
我来自未来,确实同意我过去的自己当时并不理解RAII。 - Mellester

23

通过编写以下代码,可以实现此操作:

try
{
  //.......
}
catch(...) // <<- catch all
{
  //.......
}

但这里存在一种非常不明显的风险:在try块中抛出的错误类型可能无法准确找到,因此只有在确定无论异常类型如何,程序都必须按照catch块中定义的方式继续运行时,才使用此类catch


19
可以使用

catch(...)

但这是非常危险的。在他的书中《调试Windows》中,John Robbins讲述了一个非常恶劣的错误信息被catch(...)命令掩盖的故事。你最好捕捉具体的异常。捕捉你认为try块可能会合理抛出的任何异常,但如果发生了真正意外的情况,请让代码抛出更高级别的异常。


2
我刚刚捕捉了一些用法,并在该阶段添加了一些日志记录。不对异常进行任何处理肯定会带来麻烦。 - jxramos
为什么会出现问题?另外,在 (...) 中捕获异常时,我们可以记录一些类似于 e.what() 的内容吗? - Maf

16

我在这里只是想提一下:Java

try 
{
...
}
catch (Exception e)
{
...
}

可能会漏掉一些异常!我以前真的遇到过这种情况,让人发疯;Exception从Throwable派生而来。所以,要想捕获所有东西,你不想catch Exceptions,而是要catch Throwable。

我知道听起来很吹毛求疵,但当你花了好几天时间试图找出代码中被try...catch(Exception e)包围的未捕获异常来自哪里时,它就会一直在你脑海中挥之不去。


2
当然,你永远不应该捕获Error对象——如果你应该捕获它们,它们就会成为异常。Error对象是完全致命的东西,例如堆空间耗尽等。 - SCdF
3
我们遇到了一个非常严重的错误,由于使用catch(Throwable)块而不是让它自然死亡,导致了OutOfMemoryError的捕获。 - Hakanai
1
当然,在Java中catch(Exception)可能无法捕获所有异常,您可能将其与C#混淆了... Java = catch(Thowable),C# = catch(Exception)。不要混淆它们。 - Chef Pharaoh
2
@OscarRyz听起来像是 CoderMalfunctionError(实际上是Java的一个真正的 Error 子类...尽管意思并不像它听起来那样)。 - reirab
1
这并没有给出问题的答案。如果要批评或请求作者澄清,请在他们的文章下留言。 - Donald Duck
显示剩余2条评论

12

如果您想捕获所有异常以便创建 minidump ,那么...

有人已经在Windows上完成了这项工作。

请参阅 http://www.codeproject.com/Articles/207464/Exception-Handling-in-Visual-Cplusplus 这篇文章,作者解释了他是如何找出如何捕获各种类型的异常,并提供了可行的代码。

以下是您可以捕获的列表:

 SEH exception
 terminate
 unexpected
 pure virtual method call
 invalid parameter
 new operator fault 
 SIGABR
 SIGFPE
 SIGILL
 SIGINT
 SIGSEGV
 SIGTERM
 Raised exception
C++ typed exception

使用方法: CCrashHandler ch; ch.SetProcessExceptionHandlers(); // 对于一个线程来说使用这个 ch.SetThreadExceptionHandlers(); // 每个线程都需要使用


默认情况下,这会在当前目录中创建一个迷你转储文件 (crashdump.dmp)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接