One the class X destructor is called, the object it was called on ceases to be an object of type X.
Suppose you have a class Y which inherits from X, and a class Z that inherits from Y. By the principles of object-oriented inheritance, every Y is an X. And every Z is a Y and an X.
Look at what happens when deleting an object of type Z if the destructors are called from most-derived to base:
First the Z destructor executes.
The object stops being a Z and
reverts to being only a Y.
Then the Y destructor executes. The
object stops being a Y and reverts
to being only an X.
Then the X destructor executes. The
object stops being an X, and is now
nothing at all (completely
destroyed).
Now consider what would happen if instead the base destructor was called first:
- First the
X destructor executes. The object stops being an X.
Now what is the object? It's not an X, but it can't be a Y either since a Y is an X. It can't be a Z since a Z is an X too! The object has no clearly-defined type at this point, and so invoking another method on it, even another destructor, would lead to impossible-to-define behavior.
In more concrete terms: It's entirely possible that the Z destructor or the Y destructor needs to access something that is defined in the X class. So the X destructor must run last. Note that there's no problem in the other direction since the base class X can't access anything in its derived classes.