关于c ++:虚拟析构函数和未定义的行为

Virtual destructor and undefined behavior

这个问题不同于"何时/为什么要使用virtual析构函数?".

1
2
3
4
5
6
7
8
9
10
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

问题:

  • 是否可以将其归类为未定义的行为(我们知道不会肯定调用~D())?
  • 如果~D()是空的怎么办?它会以任何方式影响代码吗?
  • 使用new[]/delete[]B* p;时,~D()肯定不会无论析构函数的virtual是什么,都会被调用。它是未定义的行为还是定义良好的行为?

  • 什么时候/为什么要使用虚拟析构函数?遵循药草缝合线指南:

    A base class destructor should be either public and virtual, or protected and nonvirtual

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

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

    C++ 03标准:5.3.5删除

    5.3.5/1

    The delete-expression operator destroys a most derived object (1.8) or array created by a new-expression.
    delete-expression:
    ::opt delete cast-expression
    ::opt delete [ ] cast-expression

    5.3.5/3:

    In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.73)

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

    请注意,这里没有Gaurantee,如果不使派生类析构函数成为虚的,就不会导致调用派生类析构函数,并且这种假设是不正确的。按照标准,一旦你在未定义行为的土地上被划过,所有的赌注都将被取消。

    注意他所说的关于未定义行为的标准。

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

    behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during
    translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]

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


  • 未定义的行为
  • (首先要注意的是,这些解构主义者通常不像你想象的那样空虚。即使解构师真的是空的(pod?),你仍然必须解构你的所有成员。,那么它仍然取决于编译器。本标准未定义。对于所有的标准关心,你的电脑可能会爆炸的删除。
  • 未定义的行为
  • 在一个要从中继承的类中,确实没有理由使用非虚拟公共析构函数。看这篇文章,准则4。

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


    正如其他人所重申的,这是完全未定义的,因为基的析构函数不是虚拟的,任何人都不能做出任何语句。有关标准的参考和进一步讨论,请参阅本主题。

    (当然,个别编纂者有权作出某些承诺,但在本例中,我没有听到任何有关承诺的消息。)

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

    给定一个基类和一个派生类(两者都没有任何虚拟方法),定义如下:

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

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


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

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

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

    共享指针的主要技巧是模板化的构造函数。