从析构函数抛出一个异常安全的vtable?

时间:2015-11-30 20:16:00

标签: c++ exception-handling

请考虑以下示例:

#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?

3 个答案:

答案 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。我写了一个小范围内容,它会分发将数据放入的引用,并在析构函数中检查错误情况。如果它看到一个它会将它转换为异常并抛出它。

这样的结构在技术上是安全的,但是在你知道自己在做什么之前,你有点想避开它。您必须明确指出,这些内容无法存储在向量或数组中,并且可能会使异常安全代码不安全。主要问题是,几乎所有人都希望所有的析构函数都不会出现。