虚析构函数和未定义行为

Virtual destructor and undefined behavior

本文关键字:未定义 析构函数      更新时间:2023-10-16

这个问题不同于" 何时/为什么应该使用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,都将被调用。它是未定义的行为还是定义良好的行为?

何时/为什么使用虚析构函数?
遵循Herb suters 指南:

基类析构函数要么是public的虚函数,要么是protected的非虚函数

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

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

c++ 03标准:5.3.5删除

5.3.5/1:

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

5.3.5/3:

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

如果~D()为空怎么办?这会对代码产生影响吗?
派生类析构函数为空可能只是让你的程序正常工作,但这再次是特定实现的实现定义的方面,从技术上讲,它仍然是一个未定义的行为。

注意,这里不能保证不使派生类析构函数为虚函数就不会导致调用派生类析构函数,这个假设是不正确的。根据标准,一旦你越过未定义行为区域,所有赌注都将取消。

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

c++ 03标准:1.3.12未定义行为未定义)

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

)

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

    未定义的行为首先要注意的是,这些解构器通常不像你想象的那样空。您仍然需要解构所有成员),即使解构器确实是空的(POD?),那么它仍然取决于您的编译器。它没有被标准定义。对于所有标准的关心,你的电脑可能会在删除时爆炸。未定义的行为

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

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

正如其他人重申的那样,这是完全未定义的,因为基类的析构函数不是虚函数,任何人都不能做任何声明。参考这个标准和进一步的讨论。

(当然,每个编译器都有权做出某些承诺,但我还没有听到任何关于这种情况的消息。)

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

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

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

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

(我想我可能会删除我的另一个答案)

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

    shared_ptr<B> p(new D);
    p.reset(); // To release the object (calling delete), as it's the last pointer.
shared_ptr的主要技巧是模板化的构造函数