捕获所有未处理的C++异常?

28

有没有一种方法可以捕获那些未被处理的异常(包括在catch块之外抛出的异常)?

我并不真正关心使用异常时通常需要进行的所有正常清理工作,只关注如何捕获异常、将其写入日志/通知用户并退出程序,因为在这些情况下,异常通常是致命的、无法恢复的错误。

类似于:

global_catch()
{
    MessageBox(NULL,L"Fatal Error", L"A fatal error has occured. Sorry for any inconvience", MB_ICONERROR);
    exit(-1);
}
global_catch(Exception *except)
{
    MessageBox(NULL,L"Fatal Error", except->ToString(), MB_ICONERROR);
    exit(-1);
}
8个回答

26

请查看std::set_terminate()

编辑:这里有一个完整的例子,包含异常匹配:

#include <iostream>
#include <exception>
#include <stdexcept>

struct FooException: std::runtime_error {
    FooException(const std::string& what): std::runtime_error(what) {}
};

int main() {
    std::set_terminate([]() {
        try {
            std::rethrow_exception(std::current_exception());
        } catch (const FooException& e) {
            std::cerr << "Unhandled FooException: " << e.what() << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "Unhandled exception: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Unhandled exception of unknown type" << std::endl;
        }

        std::abort();
    });

    throw FooException("Bad things have happened.");
    // throw std::runtime_error("Bad things have happened.");
    // throw 9001;
}

1
你能在 terminate_handler 中访问异常吗? - Konstantin A. Magg
1
@KonstantinA.Magg 我不这么认为。由于异常可以是任何类型,因此真的没有办法访问它... - kralyk
4
可以使用 std::uncaugth_exception() 和 std::current_exception()。 - Hassen Dhia
@HassenDhia,怎么办呢?uncaugth_exception()只是一个标志,而current_exception()返回一个exception_ptr(假设它在terminate处理程序中起作用),在这个阶段似乎你几乎不能对其进行任何操作,除了可能将其转换为bool。就我所理解的,C++似乎不允许你在没有合适catch块的情况下进入其内容。 - Sz.
1
@ Sz。您可以使用 try { std :: rethrow_exception(the_pointer); } catch(const YourException&e) { ... } - kralyk
@kralyk,很好。 (可惜我已经为您以前的“我不这么认为。由于异常可以是任何类型,所以真的没有办法访问它...”评论投了赞成票,那个赞成票应该转移到您的这个新评论中…没关系,我先复制一下。 :)) - Sz.

26

这可以用来捕获意外的异常。

catch (...)
{
    std::cout << "OMG! an unexpected exception has been caught" << std::endl;
}

如果没有try catch块,我认为你不能捕获异常,因此请构造程序以便将异常抛出的代码置于try/catch的控制下。


9
性能并不是一个问题,只有当实际抛出异常时系统才需要考虑如何处理。当对象超出范围时,堆栈展开是应用程序必须执行的操作。此外,试一下 - 在主函数中加入try/catch非常容易,看看是否会对性能造成影响。 - gbjbaanb
3
在现代编译器中使用异常几乎没有额外的成本,除非抛出了一个异常。此时,它的成本与使用其他查找和报告错误的方法一样昂贵。 - Martin York
6
全局catch(...)仍然捕获不到全局构造函数中的异常。例如,全局std::vector<int>(size_t(-1))将在main()被调用之前耗尽内存。 - MSalters
3
解释:为了解决这个问题的一种技术是将全局变量指针化,并在主函数中进行分配内存。翻译:绕过这个问题的一种方法是将全局变量变成指针,并在主函数中进行分配内存。 - EvilTeach
显然,为了捕获“所有未处理的C++异常”,必须针对每个线程进行此操作,这并不总是容易实现的。 - Pavel P
显示剩余7条评论

12

您可以在Windows上使用SetUnhandledExceptionFilter,它将捕获所有未处理的SEH异常。

一般情况下,这对于解决所有问题都足够了,因为据我所知,所有C++异常都是以SEH实现的。


9
没有任何catch块,你将无法捕获任何异常。你可以在main()函数中(以及每个附加线程中的等效函数)拥有一个catch(...)块。在这个catch块中,你可以恢复异常详细信息并对其进行处理,例如记录日志和退出。
然而,通用的catch(...)块也有缺点:系统发现异常已被你处理,所以不再提供任何帮助。在Unix/Linux上,这种帮助包括创建CORE文件,你可以将其加载到调试器中并查看未预期异常的原始位置。如果你使用catch(...)处理它,这些信息已经丢失了。
在Windows上,没有CORE文件,因此建议使用catch(...)块。从该块中,你通常会调用一个函数来恢复实际异常:
std::string ResurrectException()
   try {
       throw;
   } catch (const std::exception& e) {
       return e.what();
   } catch (your_custom_exception_type& e) {
       return e.ToString();
   } catch(...) {
       return "Ünknown exception!";
   }
}


int main() {
   try {
       // your code here
   } catch(...) {
       std::string message = ResurrectException();
       std::cerr << "Fatal exception: " << message << "\n";
   }
}

2
Windows有.dmp文件,它们大致相当于核心文件,但如果用户点击“发送”,它们会上传到Windows错误报告网站,而不是弄脏用户的硬盘。此外,如果您配置了即时调试器,Windows将会进入调试器。 - bk1e
1
std::string的构造函数可能会抛出异常,输出到std:cerr也是如此。不过,99%的情况下你可能会很幸运。 - rxantos

7
更新: 这仅适用于c++98。
从Meyers的More Effective C++ (第76页)中,您可以定义一个函数,在函数生成未由其异常规范定义的异常时调用该函数。
void convertUnexpected()
{
    // You could redefine the exception here into a known exception
    // throw UnexpectedException();

    // ... or I suppose you could log an error and exit.
}

在您的应用程序中注册该函数:
std::set_unexpected( convertUnexpected );

如果一个函数生成的异常没有被其异常规范定义,那么您的函数convertUnexpected()将会被调用...这意味着只有在使用异常规范时才有效。;(


4
我知道这个答案是在 C++11 标准发布之前写的,但现在 std::set_unexpected 已经被废弃了。请注意,这里的“废弃”指的是该函数已不再被推荐使用。 - scrutari
1
在C++17中已完全删除。 - Gizmo

6
只要有C++11,就可以使用这种方法(参见以下示例:http://en.cppreference.com/w/cpp/error/rethrow_exception):
#include <iostream>
#include <exception>

void onterminate() {
  try {
    auto unknown = std::current_exception();
    if (unknown) {
      std::rethrow_exception(unknown);
    } else {
      std::cerr << "normal termination" << std::endl;
    }
  } catch (const std::exception& e) { // for proper `std::` exceptions
    std::cerr << "unexpected exception: " << e.what() << std::endl;
  } catch (...) { // last resort for things like `throw 1;`
    std::cerr << "unknown exception" << std::endl;
  }
}

int main () {
  std::set_terminate(onterminate); // set custom terminate handler
  // code which may throw...
  return 0;
}

这种方法还允许您自定义未处理异常的控制台输出:可以像这样实现
unexpected exception: wrong input parameters
Aborted

改为这样:

terminate called after throwing an instance of 'std::logic_error'
  what():  wrong input parameters
Aborted

在Windows上对我不起作用。调用std::uncaught_exception返回false,即使终止是由未处理的throw std::runtime_error("h");引起的。 - Diligent Key Presser

5

这是我在main()函数中经常做的事情。

int main()
{
    try
    {
        // Do Work
    }
    catch(std::exception const& e)
    {
         Log(e.what());
         // If you are feeling mad (not in main) you could rethrow! 
    }
    catch(...)
    {
         Log("UNKNOWN EXCEPTION");
         // If you are feeling mad (not in main) you could rethrow! 
    }
}

3
这是个不错的方法,但需要记住的是,它可能无法捕捉到可能出现的静态初始化异常(我认为)。 - kralyk
1
@kralyk:无法捕获静态存储期对象的构造函数/析构函数抛出的异常。在这些情况下,将调用 std::terminate() - Martin York
2
我知道...从技术上讲,使用set_terminate()可能是可能的(请参见我的答案),但由于静态初始化顺序是实现定义的,这也不能保证... - kralyk

1
在所有的异常屏障中(不仅仅是主线程),请使用 catch (...)。我建议您始终重新抛出 (...) 并将标准输出/错误重定向到日志文件,因为您无法对 (...) 进行有意义的 RTTI。另一方面,像 GCC 这样的编译器将输出关于未处理异常的相当详细的描述:类型、what() 的值等等。

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