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

时间:2011-12-22 03:46:13

标签: c++ undefined-behavior virtual-destructor

这个问题不同于'何时/为什么要使用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是什么,都会被调用。是吗 未定义的行为或明确定义的行为?

4 个答案:

答案 0 :(得分:19)

何时/为什么要使用虚拟析构函数?
关注Herb Sutters guideline

  

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

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

根据标准,它是未定义的行为,这通常会导致Derived类析构函数未被调用并导致内存泄漏,但是在未定义行为的效果之后推测它是无关紧要的,因为标准没有保护任何东西在这方面。

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

请注意,这里没有保证不使派生类析构函数虚拟不会导致对派生类析构函数的调用,并且此假设不正确。根据标准,一旦你在Undefined Behavior土地上越过,所有的赌注都将被取消。

注意他标准中关于未定义行为的内容。

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

  

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

如果只调用派生的析构函数,则由上面引用中的粗体文本控制,对于每个实现都明确保留。

答案 1 :(得分:7)

  1. 未定义的行为
  2. 首先请注意,这些解构器通常不像你想象的那样空洞。你还需要解构所有成员)即使解构函数真的是空的(POD?),那么它仍然取决于您的编译器。标准未定义。对于所有标准维护,您的计算机可能会破坏删除。
  3. 未定义的行为
  4. 实际上没有理由在类中继承非虚拟公共析构函数。请看this article,准则#4。

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

答案 2 :(得分:2)

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

(当然,个别编制者有权做出某些承诺,但在这种情况下我没有听到任何相关内容。)

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

鉴于基类和派生类,都没有任何虚拟方法,定义如下:

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

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

答案 3 :(得分: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的主要技巧是模板化构造函数。