15

这个问题不同于“我何时/为什么应该使用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[]withB* p;时,无论析构函数~D()如何,都肯定不会被调用。virtual它是未定义的行为还是定义明确的行为?
4

4 回答 4

19

何时/为什么要使用虚拟析构函数?
遵循 Herb Sutters指南

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

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

根据标准,它是未定义的行为,这通常会导致未调用派生类析构函数并导致内存泄漏,但推测未定义行为的后效应是无关紧要的,因为标准在这方面不保证任何事情.

C++03 标准:5.3.5 删除

5.3.5/1:

delete-expression 运算符破坏由 new-expression 创建的最派生对象 (1.8) 或数组。
删除表达式
:::opt delete cast-expression
::opt delete [] cast-expression

5.3.5/3:

在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义. 在第二种选择(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。73)

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

请注意,这里没有保证不将派生类析构函数设为虚拟不会导致调用派生类析构函数,并且这个假设是不正确的。根据标准,一旦您在未定义行为领域越界,所有赌注都将取消。

请注意他的标准对未定义行为的规定。

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

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

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

于 2011-12-22T04:10:49.953 回答
7
  1. 未定义的行为
  2. 首先要注意的是,这些解构函数通常并不像您想象的那样为空。您仍然必须解构所有成员)即使解构函数确实为空(POD?),那么它仍然取决于您的编译器。它没有被标准定义。对于所有标准护理,您的计算机可能会在删除时炸毁。
  3. 未定义的行为

确实没有理由在要继承的类中使用非虚拟公共析构函数。看这篇文章,指南#4。

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

于 2011-12-22T03:53:35.977 回答
2

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

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

不过我觉得很有趣,在这种情况下,我认为mallocandfree在某些情况下比newand更好地定义delete。也许我们应该改用那些:-)

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

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

如果 D 有复杂的额外成员,您可能会出现内存泄漏,但除此之外还有定义的行为。

于 2011-12-22T04:07:53.567 回答
0

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

关于该行为的一切都是未定义的。如果您想要更好地定义行为,您应该自己研究shared_ptr或实现类似的东西。以下是定义的行为,无论任何事物的虚拟性如何:

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

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

于 2011-12-22T04:49:08.510 回答