Я вижу тот же эффект, будь то виртуальный или не виртуальный.
Это потому, что вы вызываете delete
через указатель на производный тип. Если вместо этого вы сделаете деструктор общедоступным, но не виртуальным, и выполните
base* b = new derived();
delete b;
, он (скорее всего) напечатает
base ctor
derived ctor
base dtor
done
(я не могу гарантировать, что он будетprint, потому что поведение не определено при удалении объекта производного типа через указатель на базовый класс, если деструктор не является виртуальным)
В этом случае, когда компилятор может видеть как вызов new
, так ина delete
это, скорее всего, скажет вам, что у вас здесь неопределенное поведение, но если одна единица перевода просто передает вам указатель на базу, а вы вызываете delete
в другой, то нет никакого способа узнать это для компилятора. Если вы хотите быть уверенным, что не можете совершить эту ошибку, есть два способа избежать этой проблемы.
Первый - просто сделать виртуальный деструктор;тогда не будет неопределенного поведения. Но, конечно, это имеет небольшое постоянство производительности, поскольку уничтожение теперь имеет один дополнительный уровень косвенности через vtable.
Так что, если вы никогда не намереваетесь хранить объекты в (smart-) указателях на базовый класс и использовать толькополиморфизм через ссылки, то вы можете не захотеть платить эти дополнительные расходы.
Поэтому вам нужен еще один способ предотвратить случайный вызов кем-либо delete
указателя на базовый класс, когда у вас фактически есть объект производного класса: исключение возможности когда-либо вызывать delete
для объектовбазовый класс. Именно это и дает вам создание деструктора protected
: деструкторы производных классов могут по-прежнему вызывать деструктор базового класса по мере необходимости, но пользователи указателей на базовый класс больше не могут случайно delete
через этот указатель, потому что оннедоступен.