如何在栈展开过程中获取正在抛出的异常?
先看这段示例代码:
#include <exception> #include <iostream> class Foo { public: ~Foo() { std::exception_ptr ptr = std::current_exception(); if(ptr) { std::cout << "Stack unwinding due to exception"; } std::cout << "Number of uncaught exceptions: " << std::uncaught_exceptions() << "\n"; } }; int main() { try { Foo foo; throw 5; } catch(...) { std::cout << "Exception caught\n"; } }
通过std::uncaught_exceptions()可以知道当前有未捕获的异常正在触发栈展开,但std::current_exception()没法拿到异常指针——因为它只能在catch子句内部生效。所以这段代码的实际输出是:
Number of uncaught exceptions: 1 Exception caught
并不会输出"Stack unwinding due to exception"这一行。
现在的问题是:有没有其他方法能获取正在抛出的当前异常指针?如果存在多个异常,希望拿到最新/最内层的那个。另外,较新的C++标准有没有新增相关特性?
解答
在C11到C20标准中,没有直接的标准库API能在栈展开阶段(非catch块内)获取当前异常的std::exception_ptr。std::current_exception()的设计就是仅在异常处理上下文(即catch块内部)中有效,栈展开过程中调用它会返回空指针。
不过可以通过一些变通手段实现类似需求:
1. 自定义异常抛出包装器
可以封装全局的异常捕获逻辑,在抛出异常时主动保存std::exception_ptr,之后在栈展开阶段访问这个全局指针。示例如下:
#include <exception> #include <iostream> #include <mutex> std::mutex g_exception_mutex; std::exception_ptr g_current_uncaught_exception; struct ExceptionGuard { ExceptionGuard() { std::lock_guard<std::mutex> lock(g_exception_mutex); g_current_uncaught_exception = nullptr; } ~ExceptionGuard() { std::lock_guard<std::mutex> lock(g_exception_mutex); if (std::uncaught_exceptions() > 0) { // 这里仅在抛出异常时触发,保存当前异常指针 g_current_uncaught_exception = std::current_exception(); } else { g_current_uncaught_exception = nullptr; } } }; #define THROW_EXCEPTION(e) do { \ ExceptionGuard guard; \ throw e; \ } while(0) class Foo { public: ~Foo() { std::lock_guard<std::mutex> lock(g_exception_mutex); if (g_current_uncaught_exception) { std::cout << "Stack unwinding due to exception\n"; // 可以用g_current_uncaught_exception做后续处理,比如重新抛出 // std::rethrow_exception(g_current_uncaught_exception); } std::cout << "Number of uncaught exceptions: " << std::uncaught_exceptions() << "\n"; } }; int main() { try { Foo foo; THROW_EXCEPTION(5); } catch(...) { std::cout << "Exception caught\n"; } }
这种方法的局限性在于:必须通过自定义的THROW_EXCEPTION宏抛出异常才能生效,无法覆盖所有第三方库的异常抛出场景。
2. 最新C++标准的相关情况
截至C++23,标准库仍未引入能在栈展开阶段直接获取异常指针的API。虽然有相关提案讨论过扩展异常处理上下文的能力,但尚未正式纳入标准。
3. 平台特定扩展
部分编译器提供了非标准的扩展(比如GCC的__cxa_get_globals()),可以获取当前异常的内部结构,但这种方法完全依赖编译器实现,不具备可移植性,不推荐在跨平台代码中使用。
内容的提问来源于stack exchange,提问作者CygnusX1

