2011-12-22 102 views
14

这个问题不同于'何时/为什么我应该使用一个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. 在使用new[]/delete[]B* p;,该~D()肯定不会 被调用,不管virtual内斯析构函数的。它是 未定义的行为或定义明确的行为?
+1

我经常想到要问同样的事情。 (1)B没有虚拟方法,(2)B有一个虚拟方法,但是是一个非虚拟析构函数,(3)。 B有一个虚拟析构函数。显然,只有后者是明确定义的:http://stackoverflow.com/questions/2065938/virtual-destructor – 2011-12-22 03:56:48

+0

这是我的牙齿:) – mlvljr 2014-10-21 16:07:10

回答

19

何时/为什么我应该使用虚拟析构函数?
关注香草Sutters guideline

的基类的析构函数应该是公开和虚拟,或保护和非虚

是否可以将其归类为未定义的行为(我们都知道那〜D()不会被肯定地调用)?

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

C++ 03标准:5.3.5删除

5.3.5/1:

的delete表达式运算符破坏创建的最派生对象(1.8)或阵列由一个新的表达。
删除表达式:
::选择删除铸表达
::选择删除[]铸表达

5.3.5/3:

在第一如果操作数的静态类型与其动态类型不同,静态类型应该是操作数动态类型的基类,并且静态类型应该具有虚拟析构函数或行为未定义。在第二替代(删除数组)如果动态类型的对象从它的静态类型删除不同的,那么行为是undefined.73)

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

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

请注意他标准中关于未定义行为的说法。

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

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

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

+0

+1为std :: quotes;但我仍然不明白,为什么标准将其作为UB。因为它保证'〜D()'不会被调用。保证行为是UB? – iammilind 2011-12-22 04:15:18

+2

@iammilind:*既然保证〜D()不会被称为*,说谁?标准只声明析构函数是不是虚拟的,那么它就是UB,析构函数没有被调用,在**大多数实现**中是一个后效应,它不被标准化,也不被标准要求。 – 2011-12-22 04:17:35

+2

@iammilind没有保证不调用'〜D()'。标准说它是* undefined *在这种情况下会发生什么,并且可能包括编译器以某种方式插入魔法来使'〜D()'被调用!它只是从一个v表实现中得出,在大多数编译器中,派生析构函数不会被调用。 – 2011-12-22 05:05:37

7
  1. 未定义行为
  2. 其中第一个音符,这些deconstructors一般,你会觉得,你还是要解构所有成员不为空)即使解构是真正的空( POD?),那么它仍然取决于你的编译器。它由标准未定义。对于所有标准的关心你的电脑可能会炸毁删除。
  3. 未定义行为

实在没有理由在一类非虚析构函数公众一个,就是从继承。请看this article,准则#4。

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

+0

为什么它*未定义* ...是不是*定义明确*析构函数是不会被确定的? – iammilind 2011-12-22 03:56:07

+0

我想你可以依靠它不叫D的事实。但是除非D实际上是一个空类,否则我相当肯定这会导致问题,因为D的成员不会得到解构函数调用。 – Lalaland 2011-12-22 03:59:32

+1

是的。但是我的问题是,所有事情都会发生,如预期的那样**不调用'〜D()',不调用'〜D()'成员的析构函数,等等......凡未定义的东西来? – iammilind 2011-12-22 04:01:31

2

正如其他人所重申的那样,这完全没有定义,因为Base的析构函数不是虚拟的,任何人都不能发表任何声明。请参阅this thread以参考标准和进一步讨论。

(当然,个别编译器有权作出某些承诺,但我还没有听说过任何在这种情况下)。

我觉得很有趣,虽然,在这种情况下我认为mallocfree在一些情况下比newdelete更好定义。或许我们应该用这些代替:-)

如果有一个基类和派生类,两者都不具有任何虚方法,以下定义:

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

你可能会得到一个内存如果D有复杂的额外成员会泄漏,但除此之外是已定义的行为。

+0

我认为删除可能是很好定义的东西,如POD。时间去标准潜水。 – Lalaland 2011-12-22 04:09:01

+0

@EthanSteinberg,就我的理解而言,在另一个线程[link again](http://stackoverflow.com/a/2065961/146041)上的示例基于POD。 (实际上,如果一个结构只有非虚函数,它仍然可以称为POD吗?) – 2011-12-22 04:12:33

+0

是的,但我听说新的C++标准在修改POD的过程中做了很多工作,但事实证明我错了。措辞仍然是一样的,就像以前一样没有定义。 – Lalaland 2011-12-22 04:18:59

1

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

一切有关行为是不确定的。如果你想要更好的定义行为,你应该看看shared_ptr,或者自己实现类似的东西。以下是定义的行为,不管虚拟性如何:

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

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

相关问题