旧代码在析构函数中引发异常

Legacy code throws exception in destructor

本文关键字:异常 析构函数 代码      更新时间:2023-10-16

我正在维护相当旧的遗留代码。我的先驱是异常处理的先驱(<2000)。处理非标准行为的投掷和接球逻辑似乎已经奏效。

他们实现了一个很好的投掷方式:

TL_THROW_EXCEPTION(ISQL_MSG_XML_PARSER_ERROR) << msg; 

TL_THROW_EXCEPTION扩展为:

TLThrowTec::CTLThrowExceptionTechnical::ThrowT(__FILE__,__LINE__, 
ISQL_MSG_XML_PARSER_ERROR) << msg;

它在堆栈上创建一个 TLThrowTec::CTLThrowExceptionTechnical 实例,并使用 shift 操作来设置消息字符串。析构函数创建并引发异常。

2008 年 stackoverflow 中的条目解释了当年的状态: 从析构函数中抛出异常,当时似乎已经起作用了。

但是现在Visual Studio 17允许投掷,但不能再接住。

由于遗留代码是整个系统的一部分,因此它会生成日志文件条目,例如"未处理的操作系统异常"。

我想在不对源代码进行太多更改的情况下恢复捕获逻辑。最好是带 #define TL_THROW_EXCEPTION。

有没有办法重新定义宏,以便随消息一起引发异常?

如果我搜索TL_THROW_EXCEPTION这是最后一行:

匹配行数:770 匹配文件:217 搜索文件总数:3159

我不喜欢碰他们所有人。

Modern C++ 默认情况下将所有析构函数指定为非抛出。如果从任何指定为非抛出的函数引发异常,则程序将立即终止。这是因为抛出析构函数是不受欢迎的。析构函数在堆栈展开期间自动调用,而异常已在传输中。在这种情况下,再次投掷本身会终止程序。所有标准容器都会在所包含对象的析构函数抛出时警告 UB。

但是您的用例不容易触发任何这些。ThrowT对象在我看来是作为临时对象创建的,可以立即抛出一些东西,但它们不会在堆栈展开其他东西时徘徊以调用它们的析构函数。因此,我们可以将它们标记为再次抛出以恢复功能。像这样的东西...

ThrowT::~ThrowT() noexcept(false) {
// as before
}

。将使宏再次工作。

只需将异常创建类型调整为异常,并使用

#undef TL_THROW_EXCEPTION
#define TL_THROW_EXCEPTION(msg_) 
throw YourExceptionType(__FILE__, __LINE__, (msg_))

由于throw的优先级低于<<,这将首先计算聚合表达式,然后抛出其结果。

只需修复该类:

  1. std::uncaught_exceptions()的结果(数,而不是单数)保存在 ctor 中。
  2. 标记 dtornoexcept(false)以启用传播异常。
  3. 仅在调用std::uncaught_exceptions()返回保存的计数时才投入 dtor。否则,尝试构建异常会导致异常,并且将自己的异常投入混乱将std::terminate调用。

我希望您的构造函数使用std::stringstream.否则,也请考虑解决该疏忽。

有没有办法重新定义宏?

是的,只需取消定义它,然后重新定义,如下所示:

#undef  TL_THROW_EXCEPTION 
#define TL_THROW_EXCEPTION ...

当然,如果你能想到一种方法来使其成为class或函数,那么你最好在#undef它之后再做,而不是使用宏。

所以更像是:

#undef  TL_THROW_EXCEPTION 
class TL_THROW_EXCEPTION 
{ ... }

然后定义构造函数以创建提供所需行为的实例。