При удалении объекта производного класса через указатель на базовый класс, какое свойство деструктора базового класса делает операцию корректной?
Деструктор базового класса должен быть виртуальным. Тогда при удалении через указатель на базовый класс сначала вызывается деструктор производного класса, затем деструкторы базовых подобъектов.
Если деструктор базового класса не виртуальный, удаление полиморфного объекта через такой указатель приводит к неопределённому поведению.
Виртуальные функции появились в C++ для поддержки динамического полиморфизма: вызов через интерфейс базового класса должен обращаться к реализации фактического производного типа.
Деструктор является особым методом, но для него действует тот же принцип. Если объект используется полиморфно и его время жизни завершается через базовый интерфейс, механизм уничтожения должен учитывать динамический тип объекта.
Предположим, фабрика возвращает указатель на базовый класс, фактически создав объект производного класса. Производный объект может владеть ресурсами: памятью, файловым дескриптором, соединением или блокировкой.
При удалении через базовый указатель невиртуальный деструктор не обеспечивает корректное полиморфное уничтожение. Реальный результат не следует сводить только к пропуску деструктора производного класса: сама операция имеет неопределённое поведение, поэтому нельзя надёжно предсказать ни освобождение ресурсов, ни другие последствия.
Базовый деструктор объявляют виртуальным, если объект производного типа может удаляться через указатель или ссылку на базовый класс. При виртуальном деструкторе компилятор организует вызов цепочки уничтожения от самого производного класса к базовым классам.
В конце main сначала вызывается Derived::~Derived, затем Base::~Base. std::unique_ptr<Base> также корректно уничтожает объект именно потому, что деструктор базового класса виртуальный.
Виртуальный деструктор обычно делает класс полиморфным и может увеличивать размер объекта из-за служебных данных для виртуальных функций, часто реализуемых через таблицу виртуальных функций. Для классов, которые не предназначены для полиморфного удаления, виртуальность деструктора не обязательна.
Есть два корректных проектных варианта: сделать деструктор базового класса публичным виртуальным либо запретить удаление через базовый тип, например сделать деструктор защищённым и невиртуальным. Второй вариант подходит, когда базовый класс не является владельческим полиморфным интерфейсом, но требует другой модели управления временем жизни.
Сервис создаёт разные реализации хранилища через фабрику и возвращает владение как std::unique_ptr на базовый интерфейс. Одна реализация дополнительно закрывает файл в своём деструкторе.
Рассматривались два варианта. Использование невиртуального деструктора проще и не добавляет виртуальный механизм, но делает уничтожение через интерфейс некорректным. Ручное удаление через отдельную функцию возможно, однако усложняет владение и повышает риск ошибок при исключениях.
Выбран базовый интерфейс с виртуальным деструктором и автоматическое владение через std::unique_ptr. Это обеспечивает правильную цепочку деструкторов, исключает ручной delete и гарантирует освобождение ресурса при выходе объекта-владельца из области видимости.
Нет. Для удаления через указатель на базовый класс виртуальным должен быть деструктор именно базового класса. Виртуальность наследуется, поэтому после такого объявления деструкторы производных классов также участвуют в виртуальном вызове, даже если они явно не помечены ключевым словом virtual.
Нет. Это необходимо, когда объект производного класса предполагается удалять через указатель на базовый класс. Если класс не используется как полиморфный интерфейс для владения объектами, виртуальный деструктор может быть лишним; иногда базовый класс намеренно запрещает удаление через него.
Нет. Он корректно выбирает цепочку деструкторов, но не исправляет ошибки владения, двойное удаление, обращение к уже уничтоженному объекту или неправильное создание объекта. Кроме того, удалять через базовый указатель можно только объект, созданный способом, совместимым с таким удалением; для обычного динамического объекта это обычно означает соответствующее создание через new, а предпочтительным владельцем в современном C++ является подходящий умный указатель.