Los destructores virtuales son útiles cuando hagamos un delete de un objeto derivado a través de un puntero de la clase base.

Vayamos al código:

class A {
    public:
        A() {
            cout << "A::ctor" << endl;
        }
        ~A() {
            cout << "A::dtor" << endl;
        }
};

class B: public A {
    public:
        B() {
            cout << "B::ctor" << endl;
        }
        ~B() {
            cout << "B::dtor" << endl;
        }
};
...
B *b = new B;
A *a = b;
delete b;

El resultado de este fragmento de código es:

A::ctor
B::ctor
A::dtor

Como vemos no se está destruyendo B, que es la clase hija.

Ahora si declaramos como virtual el destructor de A

...
virtual ~A() {
    cout << "A::dtor" << endl;
}
...
B *b = new B;
A *a = b;
delete b;

Da como resultado:

A::ctor
B::ctor
B::dtor
A::dtor

Ahora sí se está destruyendo B.

Conclusiones

Al no ejecutarse el destructor de un objeto, se pueden crear fugas de recursos ya que el constructor de B podría reservar memoria dinámica al construir B y nunca ser liberadas al borrar el objeto de la clase B con un puntero de la clase base, (en este caso la clase A).

Para resumir, siempre declarar como virtual todos los destructores de las clases bases que se van a tratar de manera polifórmica.