4
votes

Why is the deletion of an incomplete type defined as "undefined behaviour"?

From the C++ specification; §5.3.5/5;

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.

Given the code example (I understand why it is an error);

class ABC;

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

Why is it defined as being undefined behaviour when gcc, clang and msvc all warn on it being an incomplete type? Why not just error at that point, i.e. why is it not a diagnosable error?

2
@Cyber. That is why it is an error. I want to know why it was defined as "undefined behaviour" - why it is the compiler not required to fail the compilation?Niall
The issue may actually be raised for any method in a declared but yet undefined class. However, the compiler knows that the destructor exists and may have a certain definition (the trivial one), as opposed to any other arbitrary method. The only thing it doesn't yet know is whether the destructor will be virtual or not, which may change the calling convention (this point is brought up by the answer to the other question).didierc
My guess is that the specs go the optimistic way, by saying this problem only requires a warning, with the hope that the programmer knows what he does and will provide a definition matching its expectation (that the destructor isn't virtual).didierc

2 Answers

5
votes

Because, as your quote says, it's only undefined behaviour if it has a non-trivial destructor or deallocation function. If it's incomplete, the compiler doesn't know whether or not that's the case, so doesn't know whether or not the program is well-defined.

4
votes

The expression delete p; does two things:

  1. Destroy the complete object which contains *p.
  2. Deallocate the memory used to store said object.

Item 2 may be possible when all you know is the address of the object, without any further information. The memory allocator only cares about addresses. But determining the complete object's address may be difficult; you essentially need to promise that you are actually providing the address of a complete object.

But there's more. Before deallocating the object's storage, you must run destructors (Item 1). If the destructor has no effect, then it is acceptable to not run destructors, since that has the same behaviour as if you did run them. But if running destructors does have an effect, omitting Item 1 leads do undefined behaviour, and you need to know the complete type in order to know how to run destrutors. Incidentially, you also need to know the complete type in order to determine the address of the most-derived object for Item 2.