X::X()
Y::Y()
Y::~Y()
X::~X()
Now that the destructors are virtual, dynamic dispatch occurs for the invocation of destructor. Even though xptr has been declared as a pointer to X, it points to an object of class Y.
delete xptr;
invokes the destructor Y::~Y(). And since Y is derived from X, it goes on to invoke the destructor X::~X() See this earlier example, if you are not clear on this

The lesson from this exercise is that you should always make your destructors virtual unless you have a strong reason for not doing so.