2

我们有一个类,其语义行为如下:-

struct Sample
{
  ~Sample() throw() 
  {
    throw 0;
  }
};

void f ()
{
  try
  {
    delete new Sample;
  }
  catch (...){
  }
}

我知道在 dtors 中抛出异常是邪恶的;但是放弃第 3 方库资源会引发异常(但可以立即重新获取,这很奇怪!)。还有一个资源池,比如 Sample 类的数组/容器。因此,有两种情况需要考虑:动态分配对象的销毁和动态分配的对象数组的销毁。

目前,只有在使用数组版本(池)时,应用程序才会在不同的执行点随机崩溃。我们认为这是由于内存损坏,但为什么非池化版本有效?

分配的内存会发生什么?它是未定义的行为吗?在数组的情况下会发生什么?数组的所有元素(比如第一个元素的 dtor 是否抛出)的 dtors(至少,不是内存)是否被调用?

提前致谢,

EDIT-1:好吧,我们将其追踪到一些未调用的数组元素的 dtors。但是分配的内存似乎没有问题......以下是SC22-N-4411.pdf的第5.3.5.7节)

If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will
call a deallocation function (3.7.4.2). Otherwise, it is unspecified whether the deallocation function will be
called. [ Note: The deallocation function is called regardless of whether the destructor for the object or some
element of the array throws an exception. —end note ]

<\截图>

在这种情况下,看起来内存总是被释放。我对标准的解释正确吗?

4

5 回答 5

5

在这种情况下可能会发生两件事:

  • 调用终止()
  • 未定义的行为

在这两种情况下都不能保证释放动态分配的内存(除非应用程序终止当然会将所有资源返回给操作系统)。

于 2009-05-08T08:26:25.080 回答
4

如果 dtor 由于另一个异常而在堆栈被展开时抛出异常,C++ 将终止您的应用程序。

由于几乎不可能确定在什么情况下调用 dtor,因此标准规则是永远不要从 dtor 中抛出异常。

如果您的第 3 方库抛出异常,请将其捕获到您的 dtor 中,记录它,或将其状态保存到某个静态缓存中,您可以“稍后”将其拾取,但不要让它逃出您的 dtor。

这样做,然后查看您的对象集合是否有效,这可能会导致您的崩溃。

更新

不幸的是,我不是规范律师,更喜欢渔人之友的“吸一口”的方法。

我会编写一个带有类的小应用程序,该类从堆中分配一个兆。在一个循环中,创建一个类数组,让类 dtor 抛出一个异常,并在循环结束时抛出一个 catch 异常(导致堆栈展开并调用类数组的 dtor)并观察它看到你的虚拟机使用率飙升(我很确定它会)。

对不起,我不能给你章节,但这是我的“信念”

于 2009-05-08T08:06:13.690 回答
2

既然您在评论中询问章节和经文:

15.2:3 有一个注释,说:

“如果在堆栈展开期间调用的析构函数以异常终止退出,则调用(15.5.1)。因此析构函数通常应捕获异常,而不是让它们传播出析构函数”

据我所知,在那里说“一般”的唯一理由是,可以非常仔细地编写一个程序,这样析构函数可以抛出的任何对象都不会作为堆栈展开的一部分被删除。但在普通项目中,这比“析构函数不能抛出”更难执行。

15.5.1 和 2 说:

“在以下情况下...... - 当堆栈展开(15.2)期间对象的破坏使用异常退出时......void terminate()被调用”。

15.5.1 中的 terminate() 还有一些其他条件,这些条件建议您可能不想抛出其他东西:异常的复制构造函数、atexit 处理程序和unexpected. 但是例如,复制构造函数失败的最可能原因是内存不足,例如在 linux 上可能会出现段错误,而不是无论如何抛出异常。在这种情况下 terminate() 似乎并没有那么糟糕。

在这种情况下,看起来内存总是被释放。我对标准的解释正确吗?

在我看来,被删除对象的内存总是被释放。它并不意味着它通过指针拥有并在其析构函数中释放的任何内存都被释放,特别是如果它是一个数组并且因此有几个析构函数可以调用。

哦,是的,你相信你的第三方库是异常安全的吗?免费期间的异常是否有可能使库处于其作者没有预料到的状态,并且崩溃是因为这个?

于 2009-05-08T15:14:41.827 回答
1

1)从析构函数抛出异常是不好的,因为如果正在处理异常并且发生另一个异常,应用程序将退出。因此,如果在异常处理期间您的应用程序清除了对象(例如,在每个对象上调用析构函数)并且其中一个析构函数抛出另一个异常,则应用程序将退出。

2)我认为当容器中的其他元素抛出异常时,不会自动调用析构函数。如果在容器的析构函数中抛出异常,那么其余元素肯定不会被清理,因为应用程序将在处理异常时展开堆栈。

编写析构函数的标准方式应该是这样的:

A::~A()
{
   try {
         // some cleanup code
   }
   catch (...) {} // Too bad we will never know something went wrong but application will not crash
}
于 2009-05-08T08:07:24.907 回答
1

析构函数绝不能抛出异常,它会导致未定义的行为,也可能导致内存泄漏。让我们考虑以下示例

T* p = new T[10];
delete[] p;

那么,如果 T 抛出析构函数,new[] 和 delete[] 将如何反应?

让我们首先考虑构造都顺利进行,然后在 delete[] 期间抛出第四个左右的析构函数。delete[] 可以选择传播异常,这将导致留在数组中的所有其他 T 对象丢失,不可检索,因此不可破坏。它也无法“捕获”异常,因为这样 delete 将不再是异常中性的。

其次,假设其中一个构造函数抛出。假设第 6 个构造函数抛出异常。在堆栈展开期间,必须解构到目前为止已构建的所有对象。所以调用了第 5、4、3 等等析构函数。如果第四个或第三个析构函数抛出另一个异常会发生什么?它应该被吸收还是传播?

对此没有答案,因此该主题会导致未定义的行为。

正如我在第一个示例中所述,也可能导致内存泄漏。

于 2009-05-08T08:23:21.903 回答