请考虑以下示例:
#include <csignal>
class A
{
public:
virtual ~A() {}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual ~B() { throw 5; }
virtual void foo() {}
};
int main(int, char * [])
{
A * b = new B();
try
{
delete b;
}
catch ( ... )
{
raise(SIGTRAP);
}
return 0;
}
我一直认为(天真的我),在这种情况下,当程序进入catch
段时,然后是b
指向的object B
,因为这是非常合乎逻辑的,异常将“取消”(如果安全地编程)析构函数的效果。但是当我尝试在gdb中运行这段代码并到达catch
部分的断点时,我发现B对象不见了,只剩下一个基本对象,因为vtable看起来像这样:
(gdb) i vtbl b
vtable for 'A' @ 0x400cf0 (subobject @ 0x603010):
[0]: 0x0
[1]: 0x0
[2]: 0x4008e0 <__cxa_pure_virtual@plt>
我的问题:如果我强烈地想从析构函数抛出一个异常,有没有办法避免(半)破坏vtable?
发布于 2015-12-01 04:41:43
我一直认为(天真的我),在这种情况下,当程序进入
段时,那么b指向的对象B将是完好无损的,因为非常合乎逻辑的是,异常将“取消”(如果安全地编程)析构函数的效果。
这不是真的。该标准说:
一个具有任意存储时间的对象,其初始化或销毁被异常终止,它将为其所有完全构造的子对象(不包括类联合的变量成员)执行析构函数,即对于主体构造函数(12.6.2)已完成执行而析构函数尚未开始执行的子对象执行析构函数。
(15.2/2 in N4140)
也许更重要的是:
类型为T的对象的生命周期在以下情况下结束:
-如果T是具有非平凡析构函数的类类型(12.4),则开始析构函数调用
(3.8/1.3 in N4140)
由于b
的每个成员和基础都是完全构造的,并且它们的析构函数没有进入,但它们将被认为是销毁的。因此,在您的catch
b
块中,所指向的整个对象已经是死的。
这背后的理性可能是禁止“半销毁”对象,因为不能销毁的对象的状态应该是什么还不清楚。例如,如果只有一些成员已经被销毁了呢?
甚至标准本身也建议不要留下析构函数的异常。正如我在之前的评论中所写的,抛出析构函数是很奇怪的。
我们可以从上面的引用中得到一个很好的规则:当一个对象的构造函数是done而不抛出时,它就开始存在,只要它的析构函数开始执行,它就不存在了,无论它是如何退出的。(在标准中的其他地方,这一点被重申得更清楚。这是有例外的,不要关心它们。)
所以总而言之:
如果我强烈地想从析构函数抛出一个异常,有没有办法避免(半)破坏vtable?
不是的。一旦你进入析构函数,你的对象就完成了。
发布于 2015-12-01 04:56:49
在这种情况下,当程序进入
段时,那么对象B中的b点将是完好无损的,因为从逻辑上讲,异常将“取消”(如果安全地编程)析构函数的效果。
不是的。 when its destructor 。
不能取消析构函数。
正如其他人所说,在C++中抛出析构函数是很奇怪的,你想要避免它们except for special cases。
发布于 2015-12-01 05:25:49
就实例而言,从析构函数抛出它是定义良好且安全的。您开始遇到问题的地方是数组(因为它无法完成数组的删除,并且您无法找回它)和catch子句(可能最终调用terminate)。如果析构函数抛出,编写异常安全代码也很困难(我认为这实际上是不可能的,但还没有准备好从内存中断言这一点)。
我曾经使用过抛出析构函数来做一些事情。例如,我使用的API可能会返回错误代码并分配错误blob。我写了一个小的作用域保护的东西,它会分发引用来放入数据,并在析构函数中检查错误条件。如果它看到一个异常,它会将其转换为异常并抛出它。
像这样的构造在技术上是安全的,但在你知道自己在做什么之前,你会想要避免它。您必须明确,这些内容不能存储在向量或数组中,并且可能会使异常安全代码不安全。主要的问题是,几乎每个人都希望所有的析构函数都是非抛出的。
https://stackoverflow.com/questions/34007190
复制相似问题