Если с некоторого класса в иерархии наследования метод стал виртуальным, то во всех производных от него классах он будет виртуальным, вне зависимости от того, указано ли ключевое слово virtual в классах наследниках.
С*с = new C ();
A*a = c;
a->hgj(); // Напечатает "class C"
B*b = c;
b->hgj(); // Напечатает "class C"
Это оно и есть полиморфизм.
Если в классе есть виртуальные функции, значит должен быть виртуальный деструктор. Если это не сделать будут проблемы с утечками памяти при работе new и delete с указателями.
С*с = new C ();
A*a = c;
B*b = c;
delete b; // Получится memory leak без virtual ~B - не будет вызываться ~C !!!!
// И все ОК при virtual ~B - ~C вызывается
Посмотрите вывод программы
P.S. Хотя в вашем примере вызов деструкторов будет правильный, так как ~A уже виртуальный.
А вот если вообще убрать virtual у ~, тогда будут утечки памяти.