为什么删除不完整类型被定义为“未定义行为”?

4
为什么删除不完整类型的对象会被定义为“未定义行为”?
来自C++规范; §5.3.5/5;
如果要删除的对象在删除点具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的。
给定以下代码示例(我理解为什么这是一个错误);
class ABC;

int main()
{
    ABC* p = nullptr;
    delete p;
}

为什么在gcc、clang和msvc上都会警告不完整类型的情况下,它被定义为“未定义行为”?为什么不直接在那个点上报错,即为什么它不是可诊断错误?

可能是这个问题的重复:为什么删除不完整的类型是未定义行为? - Cory Kramer
@Cyber。这就是为什么会出现错误。我想知道为什么它被定义为“未定义行为” - 为什么编译器不需要在编译时失败? - Niall
问题实际上可能针对已声明但未定义的任何类中的任何方法提出。然而,编译器知道析构函数存在并且可能具有某些定义(与任何其他任意方法相反的是,它可能是平凡的)。唯一还不知道的是析构函数是否为虚拟的,这可能会改变调用约定(这一点由另一个问题的答案提出)。 - didierc
1
我的猜测是规格说明采用乐观的方式,只要求发出警告,希望程序员知道自己在做什么,并提供与其期望相匹配的定义(即析构函数不是虚函数)。 - didierc
2个回答

5

因为如您所引用的那样,只有在具有非平凡析构函数或释放函数时才会出现未定义行为。如果它是不完整的,编译器不知道是否是这种情况,因此也不知道程序是否定义良好。


所以基本上你的意思是唯一明确定义的情况只适用于POD类型? - Niall
1
@Niall:不是的。你引用的内容是说,对于任何具有平凡析构函数和无需分配函数的类型,它都是明确定义的。 - Mike Seymour
@Niall 注意,一个类可以拥有非平凡的复制构造函数(使其成为非POD),同时拥有平凡的析构函数是完全可能的。 - Angew is no longer proud of SO

4
表达式delete p;有两个作用:
  1. 销毁包含*p的完整对象。
  2. 释放用于存储该对象的内存。
第2项可能在您只知道对象地址而没有其他信息时成立。内存分配器只关心地址。但是确定完整对象的地址可能很困难;您基本上需要承诺实际提供完整对象的地址。
但还有更多。在释放对象的存储之前,必须运行析构函数(第1项)。如果析构函数没有效果,则可以不运行析构函数,因为这与运行它们具有相同的行为。但是,如果运行析构函数会产生影响,则省略第1项会导致未定义的行为,并且您需要知道完整类型以了解如何运行析构函数。顺便说一下,您还需要知道完整类型才能确定第2项中最派生对象的地址。

那么这里唯一的“定义”就是析构函数没有效果,然后内存的释放就是发生的一切? - Niall
@Niall:是的,需要澄清的是,通过多态基类指针销毁对象始终是“一种影响”。 - Kerrek SB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接