虚拟析构函数和未定义的行为

这个问题不同于“ 何时/为什么我应该使用virtual析构函数? ”。

 struct B { virtual void foo (); ~B() {} // <--- not virtual }; struct D : B { virtual void foo (); ~D() {} }; B *p = new D; delete p; // D::~D() is not called 

问题

  1. 这可以被归类为一个未定义的行为(我们知道~D()不会被肯定的调用)?
  2. 如果~D()是空的。 它会以任何方式影响代码?
  3. B* p;使用new[] / delete[] B* p;~D()肯定不会被调用,而不pipe析构函数的virtual性如何。 这是一个未定义的行为或明确的行为?

何时/为什么我应该使用虚拟析构函数?
按照香草Sutters 准则

基类析构函数应该是public和virtual,或者是protected和nonvirtual

这可以被归类为一个未定义的行为(我们知道〜D()不会被肯定的调用)?

它是根据标准的未定义的行为,通常会导致Derived类析构函数不被调用并导致内存泄漏,但是在未定义行为的效率之后进行推测是没有意义的,因为标准在这方面没有任何规定。

C ++ 03标准:5.3.5删除

5.3.5 / 1:

delete-expression操作符销毁最大派生对象(1.8)或由new-expression创build的数组。
删除expression式:
:: opt delete cast-expression
:: opt delete [] cast-expression

5.3.5 / 3:

在第一种select(删除对象)中,如果操作数的静态types不同于其dynamictypes,则静态types应该是操作数dynamictypes的基类,静态types应该具有虚拟析构函数或行为未定义。 在第二个select(删除数组)中,如果要删除的对象的dynamictypes与静态types不同,则行为是不确定的。

如果~D()是空的。 它会以任何方式影响代码?
仍然是未定义的行为按照标准,派生类析构函数为空可能只是让你的程序正常工作,但这又是一个特定实现的实现定义的方面,从技术上讲,它仍然是一个未定义的行为。

请注意,在这里没有gaurantee,没有使派生类析构函数虚拟只是不会导致调用派生类析构函数,这种假设是不正确的。 根据标准,所有投注都会在未定义的行为状态下被越过。

请注意他的标准是关于未定义的行为。

C ++ 03标准:1.3.12未定义的行为[defns.undefined]

行为,例如在使用错误的程序结构或错误的数据时可能出现的情况,本标准对此没有要求。 本标准忽略对行为的任何明确定义的描述时,也可能会出现未定义的行为。 [ 注意:允许的未定义的行为范围从完全忽略情况,具有不可预知的结果,在翻译或程序执行过程中以环境特征(有或者没有发布诊断消息),终止翻译或执行发出诊断信息)。 许多错误的程序结构不会产生未定义的行为; 他们需要被诊断。 ]

如果只有派生的析构函数不会被调用,则由上面引用中的粗体文本控制,这对每个实现都是明确的。

  1. 未定义的行为
  2. 首先要注意的是,这些解构器一般不会像你想的那样空洞,你仍然需要解构所有的成员 )即使解构器是真的空的,它仍然依赖于你的编译器。 标准没有定义。 对于所有标准的关心你的电脑可能会炸毁删除。
  3. 未定义的行为

真正没有理由在一个类中的非虚拟公共析构函数被inheritance。 看这篇文章 ,准则#4。

使用受保护的非虚拟析构函数和shared_ptrs(它们具有静态链接)或公共虚拟析构函数。

正如其他人所重申的那样,这完全没有定义,因为基地的破坏者不是虚拟的,任何人都不能发表任何言论。 看到这个线程的参考标准和进一步的讨论。

(当然,个别编译者有权作出某些承诺,但在这种情况下,我还没有听到任何有关这方面的信息。)

我觉得有趣的是,在这种情况下,我认为mallocfree在某些情况下比newdelete更好定义。 也许我们应该使用这些:-)

给定一个基类和派生类, 它们都没有任何虚方法 ,定义如下:

 Base * ptr = (Base*) malloc(sizeof(Derived)); // No virtual methods anywhere free(ptr); // well-defined 

如果D有复杂的额外成员,则可能会发生内存泄漏,但除此之外,这是已定义的行为。

(我想我可能会删除我的其他答案。)

关于这个行为的一切都是未定义的。 如果你想要更好的定义行为,你应该看看shared_ptr ,或者自己实现类似的东西。 以下是定义的行为,不pipe任何事情的虚拟性:

  shared_ptr<B> p(new D); p.reset(); // To release the object (calling delete), as it's the last pointer. 

shared_ptr的主要技巧是模板化的构造函数。