Consider the following:
In X.h:
class X
{
X();
virtual ~X();
};
X.cpp:
#inclu
In C++ functions have to be defined if and only if they are used in your program (see the ODR in 3.2/2). In general, non-virtual functions are used if they are called from potentially evaluated expressions. Any non-pure virtual function is considered unconditionally used. When [non-virtual] special member functions are used is defined in dedicated locations of the language standard. And so on.
In your first example, you declared your destructor as a non-pure virtual function. This immediately means that your destructor is used in your program. This, in turn, means that the definition of that destructor is required. You failed to provide a definition, so the compiler reported an error.
In your third example the destructor is non-virtual. Since you are not using the destructor in your program, no definition is required and the code compiles (see 12.4 for detailed description of what constitutes a use of a destructor).
In your second example you are dealing with a quirk of the implementation, triggered by the fact that the constructor is inlined. Since the destructor is non-pure virtual, the definition is required. However, your compiler failed to detect the error, which is why the code seems to compile successfully. You can dig for the reasons of this behavior in the details of the implementation, but from the C++ point of view this example is as broken as the first one for exactly the same reason.