我们有一个类的语义行为如下: -
struct Sample
{
~Sample() throw()
{
throw 0;
}
};
void f ()
{
try
{
delete new Sample;
}
catch (...){
}
}
我知道在dtors中抛出异常是邪恶的;但放弃第三方图书馆资源是一个例外(但可以立即重新获得,有些奇怪!)。还有一个这个资源的池,比如一个Sample类的数组/容器。因此,有两种情况需要考虑:破坏动态分配的对象和破坏动态分配的对象数组。
目前,只有在使用阵列版本(池)时,应用程序才会在不同的执行点随机崩溃。我们认为这是由于内存损坏,但那么为什么非版本化版本有效呢?。
分配的内存会怎样?是不确定的行为?在阵列的情况下会发生什么?是否会调用数组中所有元素的dtors(至少,而不是内存)(比如第一个元素的dtor是否被抛出)?
提前致谢,
EDIT-1: 好吧,我们跟踪了一些没有被调用的数组元素。但分配的内存似乎没有问题...... 以下是SC22-N-4411的第5.3.5.7节.pdf)
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 ]
< \&剪断GT;
在这种情况下,看起来总是释放内存。我正确地解释标准吗?
答案 0 :(得分:5)
在这种情况下可能会发生两件事:
在任何情况下都不能保证释放动态分配的内存(除了应用程序终止当然会将所有资源都返回给操作系统)。
答案 1 :(得分:4)
如果由于另一个异常导致堆栈被解除时,如果dtor抛出异常,则C ++将终止您的应用程序。
由于几乎不可能确定在什么情况下调用dtor,标准规则是从不从dtors中抛出异常。
如果您的第三方图书馆抛出异常,请在您的dtor中捕获它,记录它,或将其状态保存到某个静态缓存中,您可以“稍后”将其保存,但不要让它从你的dtor。
执行此操作,然后查看您的对象集合是否有效,可能会导致崩溃。
<强>更新强>
不幸的是,我不是一名规范律师,更喜欢采用“{3}}”的方式“吸一看”。
我会写一个小应用程序,其中一个类从堆中分配一个meg。在循环中,创建类的数组,使类dtor抛出异常,并在循环结束时抛出异常(导致堆栈展开并调用类数组的dtors)并观察它看看你的虚拟机使用情况(我很确定会这样)。
对不起,我不能给你章节和经文,但这是我的“信念”
答案 2 :(得分:2)
因为你在评论中提到章节和诗句:
15.2:3有一张纸条,说:“如果在堆栈展开期间调用的析构函数退出并且异常终止被调用(15.5.1)。所以析构函数通常应该捕获异常而不是让它们从析构函数中传播出来”
据我所知,在那里说“一般”的唯一理由是,可以非常仔细地编写一个程序,这样就不会删除析构函数可以抛出的对象,作为堆栈展开的一部分。但是,对于普通项目来说,这是一个更难实施的条件,而不是“破坏者必须抛弃”。
15.5.1和2说:
“在以下情况下...... - 当使用异常退出堆栈展开(15.2)期间对象的销毁时... void terminate()
被称为”。{/ p>
15.5.1中的terminate()还有一些其他条件,它们建议你可能不想抛出的其他东西:复制异常的构造函数,atexit处理程序和unexpected
。但是,例如,复制构造函数失败的最可能原因是内存不足,例如, linux可能是段错误而不是抛出异常。在这种情况下,terminate()似乎并不那么糟糕。
在这种情况下,看起来总是会释放内存。我是否正确解释标准?
向我看,好像要删除的对象的内存总是被释放。它并不意味着它通过指针拥有的任何内存,以及它在析构函数中的释放,都会被释放,特别是如果它是一个数组,因此有几个析构函数可以调用。
哦,是的,你相信你的第三方图书馆是例外安全的吗?免费期间的例外是否有可能使图书馆处于其作者未预料到的状态,并且崩溃是因为这个原因?
答案 3 :(得分:1)
1)从析构函数中抛出异常是不好的,因为如果正在处理异常并且发生另一个异常,则应用程序将退出。因此,如果在异常处理期间,您的应用程序清除对象(例如,在每个对象上调用析构函数),并且其中一个析构函数抛出另一个异常,则应用程序将退出。
2)当其中一个元素抛出异常时,我不认为析构函数会自动调用容器中的其余元素。如果在容器的析构函数中抛出异常,那么其余元素肯定不会被清除,因为应用程序在处理异常时将展开堆栈。
编写析构函数的标准方法应该是:
A::~A()
{
try {
// some cleanup code
}
catch (...) {} // Too bad we will never know something went wrong but application will not crash
}
答案 4 :(得分:1)
析构函数绝不能抛出异常,导致未定义的行为,也可能导致内存泄漏。 让我们考虑以下示例
T* p = new T[10];
delete[] p;
那么,如果T抛出析构函数,new []和delete []会如何反应?
让我们首先考虑构造都顺利进行,然后在delete []期间,第四个左右的析构函数抛出。 delete []可以选择传播异常,这会导致数组中遗留的所有其他T对象丢失,无法检索,因此无法销毁。 它也不能“捕获”异常,因为删除不再是异常中立的。
其次,说其中一个构造函数抛出。假设第6个构造函数抛出异常。在堆栈展开期间,必须解构迄今为止构造的所有对象。因此第5,第4,第3等析构函数被调用。 如果第4或第3个析构函数抛出另一个异常会发生什么?应该是吗? 吸收还是传播?
对此没有答案,因此该主题会导致未定义的行为。
正如我的第一个例子所述,也可能导致内存泄漏..