删除完整的 class 类型会导致未定义的行为吗?

Could deleting complete class type result in undefined behavior?

我发现自己在理解 5.3.5$5 中引用自 C++ 标准的以下句子时遇到了麻烦:(重点是我的)

If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

我知道这个删除不完整类型的问题已经在 SO 中讨论过好几次了,我可以理解为什么删除不完整 class 类型是未定义的行为。这个Q&A很好的解释了。

我无法理解的是有关完整 class 类型的部分。这是否意味着删除一个完整的对象 class 具有非平凡的析构函数或释放函数是未定义的行为?如果是这样,请提供一些代码来说明它可能导致的未定义行为。

让我尝试使用以下代码解释我的理解:

struct Foo;
struct Bar;

Foo* newFoo();
Bar* newBar();

int main()
{
   // Get a pointer to Foo, somehow
   Foo* fp = newFoo();

   // Get a pointer to Bar, somehow
   Bar* bp = newBar();

   // Foo is an incomplete class at this point.
   // This is OK since Foo has a trivial destructor.
   delete fp;

   // Bar is an incomplete class at this point.
   // Not OK since Bar has a non-trivial destructor.
   // This is cause for undefined behavior.
   delete bp;
}

struct Foo {};

struct Bar {
   Bar() { data = new int[10]; }
   ~Bar(){ delete [] data; }
   int* data;
};

Foo* newFoo()
{
   return new Foo;
}

Bar* newBar()
{
   return new Bar;
}

Does it mean deleting a object of complete class has a non-trivial destructor or a deallocation function is undefined behavior?

不,因为那意味着

class Foo {
public:
    ~Foo() { /*do something*/ };
};

是未定义的行为,事实并非如此。

您似乎已经知道删除具有不完整 class 类型的对象是未定义的行为。问题是,它只是未定义的行为 if class 有一个不平凡的 destructor/deallocation.

在这种情况下不是未定义的行为:

//Foo is forward declared somewhere and destructed
//This is the class definition not available at the point that it is destructed
class Foo {
public:
    //~Foo() = default; 
};

只是在这种情况下是未定义的

class Foo {
public:
    ~Foo() { /*do something*/ };
};

请注意,即使 Foo 有一个平凡的 destructor/deallocation,如果 Foo 继承自另一个 class 有一个不平凡的 destructor/deallocation,它仍然是未定义的行为。

在相关案例中,未定义行为有两个先决条件:

  1. 正在通过指向不完整类型的指针删除对象;
  2. 正在删除的对象的完整 class 具有非平凡的析构函数或(用户定义的)释放函数。

如果这些条件中的任何一个为假,则没有未定义的行为(至少由于正在讨论的问题)。

这特别意味着

  1. 删除完整类型的对象是安全的(因为会执行正确的析构and/or释放函数)

  2. 通过指向不完整类型的指针删除具有普通析构函数且没有用户定义的释放函数的对象是安全的(因为在没有完整类型信息的情况下,编译器不会调用析构函数并使用默认的释放函数,这与通过指向完整类型的指针执行删除时发生的情况完美匹配。