Virtual destructor and undefined behavior
这个问题不同于"何时/为什么要使用
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 |
问题:
什么时候/为什么要使用虚拟析构函数?遵循药草缝合线指南:
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)
如果
请注意,这里没有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. ]
如果只有派生的析构函数不会被调用,则由上面的引号中的粗体文本控制,这显然是为每个实现保留的。
在一个要从中继承的类中,确实没有理由使用非虚拟公共析构函数。看这篇文章,准则4。
使用受保护的非虚拟析构函数和共享指针(它们具有静态链接)或公共虚拟析构函数。
正如其他人所重申的,这是完全未定义的,因为基的析构函数不是虚拟的,任何人都不能做出任何语句。有关标准的参考和进一步讨论,请参阅本主题。
(当然,个别编纂者有权作出某些承诺,但在本例中,我没有听到任何有关承诺的消息。)
不过,我觉得有趣的是,在这种情况下,我认为在某些情况下,
给定一个基类和一个派生类(两者都没有任何虚拟方法),定义如下:
1 2 | Base * ptr = (Base*) malloc(sizeof(Derived)); // No virtual methods anywhere free(ptr); // well-defined |
如果D有复杂的额外成员,可能会发生内存泄漏,但除此之外,这是定义的行为。
(我想我可以删除我的其他答案。)
关于这种行为的一切都是不明确的。如果您想要更好地定义行为,您应该研究
1 2 | shared_ptr p(new D); p.reset(); // To release the object (calling delete), as it's the last pointer. |
共享指针的主要技巧是模板化的构造函数。