请考虑以下示例:
#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
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?
答案 0 :(得分:17)
我一直认为(天真的我)当程序进入这种情况时,进入catch部分,然后b点的对象B将是完整的,因为异常将被“取消”(如果已编程)是合乎逻辑的安全地)析构函数的效果。
事实并非如此。标准说:
任何存储持续时间的对象,其初始化或销毁由异常终止 为所有完全构造的子对象执行析构函数(不包括a的变体成员) 类似于union的类,也就是主要构造函数(12.6.2)已完成执行的子对象 析构函数尚未开始执行。
(N4140中的15.2 / 2)
,可能更重要的是:
类型T的对象的生命周期在以下时间结束:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始
(N4140中3.8 / 1.3)
由于b
的每个成员和基地都是完全建造的而且没有进入他们的析构者,他们将被视为被摧毁。 因此,在catch
区块中,指向的整个对象b
已经死亡。
这背后的理性可能是禁止“半破坏”的物体,因为不清楚物体的状态应该是什么。例如,如果只有一些成员已经被销毁了呢?
即使标准本身也建议不要使用析构函数。正如我之前在评论中写的那样,抛出析构函数很奇怪。
我们可以从上面的引文中得出一个好的规则:当一个对象的构造函数完成而没有抛出时,它就会开始存在,并且一旦它的析构函数它就不再存在开始执行,无论它如何退出。(这在标准的其他地方更清楚地重申。有例外,不关心它们。)
总之:
如果我热衷于从析构函数中抛出异常,有没有办法避免(半)破坏vtable?
没有。输入析构函数后,您的对象就会完成。
答案 1 :(得分:6)
当程序进入此时 case,进入catch部分,然后是b点的对象B. 完整,因为异常将具有相当合理的逻辑 &#34;取消&#34; (如果安全编程)析构函数的效果。
没有。 lifetime of an object ends when its destructor starts。
您无法取消析构函数。
正如其他人所说,在C ++中抛出析构函数很奇怪,你想要避免它们except for special cases。
答案 2 :(得分:0)
就该实例而言,从析构函数抛出是很好的定义和安全。你开始遇到问题的地方是在数组中(因为它无法完成删除数组而你无法取回它)和catch子句(最终可以调用terminate)。如果析构函数抛出,那么编写异常安全代码也很困难(我认为它实际上是不可能的,但还没准备好从内存中声明)。
我已经使用了抛出析构函数来做事情。例如,我正在使用可能返回错误代码并分配错误blob的API。我写了一个小范围内容,它会分发将数据放入的引用,并在析构函数中检查错误情况。如果它看到一个它会将它转换为异常并抛出它。
这样的结构在技术上是安全的,但是在你知道自己在做什么之前,你有点想避开它。您必须明确指出,这些内容无法存储在向量或数组中,并且可能会使异常安全代码不安全。主要问题是,几乎所有人都希望所有的析构函数都不会出现。