我今天在工作中遇到了一些代码,其中析构函数抛出了异常。
- 你永远不应该从析构函数中抛出异常。我指出这一点是因为我确信如果我不抛出异常,其他人也会抛出异常。
我被告知这是一个明智的设计决策,需要在遇到另一个故障的情况下清理一些数据。正在利用堆栈展开的过程来进行清理。在正常情况下,清理过程是成功的,不会抛出异常。但今天我遇到了一个清理失败并抛出异常的情况,因此我开始调查。
- 除上述内容外,由于这不是一个关于我所工作的组织使用的代码审查代码的问题,因此我的问题如下。*
如果引发异常后的代码路径引发了另一个异常,会发生什么情况?
由于这是一个不寻常的情况,我只知道2种方式,这可能发生。
1.第一种情况是抛出一个异常,它被catch块捕获,然后抛出。这与抛出一个未被捕获的异常是一样的。关于这一点已经有一些问题,例如here。简而言之,调用了terminate()
。
1.当抛出一个异常时,Stack Unwinding process就开始了。这个过程调用栈分配对象的析构函数。因此,我所知道的引发嵌套异常的唯一其他方法是在析构函数内部抛出,就像我今天遇到的一样。
我想不出任何进一步的可能性。如果有任何可能性的话,我很有兴趣听听。
关于第二点,在这种情况下会发生什么?
2条答案
按热度按时间oprakyz71#
如果引发异常后所遵循的代码路径引发了另一个异常,会发生什么情况?
这取决于第二个异常触发的确切时间和地点。在C++中,你可以有“无限”数量的当前抛出的未捕获异常,并且一切都必须正常工作。这使得实现非常复杂。
1.这是不正确的,
catch
块可以在不调用terminate
的情况下引发异常,甚至可以通过throw;
显式支持。std::terminate
,但“嵌套”异常是支持的,如下所述。catch
区块中的建构函式(如果由值撷取)掷回。一般来说,异常处理是:
throw
语句,则从表达式创建异常对象E
并将其存储在 * 某处 *,比如说“可能不在堆栈上”。1.找到与异常处理程序匹配的
catch
块处理程序,并将控制转移到它。如果这样的处理程序不存在,则调用std::terminate
。1.在控制转移期间,相关局部对象以其构造的相反顺序被适当地破坏。
catch
块的参数是从E
构造的,异常现在被认为是捕获的,并且catch
块开始执行。块内的任何throw
都是新的异常,并且进程重新开始。标准没有指定2、3的顺序。编译器可以首先搜索处理程序,如果没有找到,则立即终止程序,在这种情况下不保证发生栈展开。AFAIK gcc,clang现在不展开栈。
栈展开调用本地对象的析构函数,而本地对象的析构函数可以调用更多的函数。这些函数可以抛出,导致更多的栈展开和更多的析构函数调用,这又会导致更多的异常......这是一种非常有效的方法,可以让任意数量的挂起的、尚未处理的异常同时处于活动状态。实际上,你可以通过调用
std::uncaught_exceptions
来查询该数量唯一的问题是当抛出的未捕获异常逃逸到前一个(仍然)未捕获的挂起异常的堆栈展开过程中时--也就是当它从析构函数抛出时,**直接被堆栈展开算法调用,**因为在里面没有找到任何匹配的处理程序。这种情况导致
std::terminate
调用,因为没有其他合理的方法可以做。支持从析构函数本身引发,并且可以捕获,但由于您不知道触发该调用的确切原因,因此实际上强烈建议不要这样做
会起作用的,但请假装你没有读过。一个人也应该问问自己,如果一个物体拒绝被摧毁,这意味着什么。
aiazj4mn2#
我在上面提出问题的方式使答案看起来,也许,并不那么令人惊讶。
特别是,当我开始研究这一点时,我没有考虑未捕获异常的情况,如上面的第1点。
考虑到这一点,两种情况下的结果相同可能就不足为奇了。调用
terminate()
。这里有一个Godbolt demonstration的链接。Executor窗格显示在编译时产生了一个警告,并且调用了terminate。这个信息可能是从操作系统返回的。
进一步调查发现,
terminate()
函数默认设置为调用abort()
,而不是that this can be changed。然后
abort()
似乎向操作系统SIGABRT
发送了一个信号,该信号在Linux系统上的值为134或6。我发现了关于此问题的冲突信息,我不确定哪个是正确的。这里有一些参考资料,我会及时更新。
如果你有一个错误代码,那么你就可以在这个错误代码中找到一个错误代码,并将它作为一个结果终止。
https://www.man7.org/linux/man-pages/man7/signal.7.html
https://www.man7.org/linux/man-pages/man3/abort.3.html
我在内联上发布了这个链接,但它也提供了一些关于可以调用
terminate()
的可能方法的细节。https://www.ibm.com/docs/en/zos/2.1.0?topic=functions-terminate-function
在我的Linux测试机器上,它使用Linux内核5.10.0-18-amd 64,
abort()
打印Aborted
,echo $?
打印134
。