Хотя язык C++ автоматически предоставляет деструкторы для ваших классов, если вы не предоставляете их самостоятельно, все же иногда вы можете сделать это сами.
Виртуальные деструкторы
При работе с наследованием ваши деструкторы всегда должны быть виртуальными. Рассмотрим следующий пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream> class Parent { public: ~Parent() // примечание: Деструктор не виртуальный { std::cout << "Calling ~Parent()" << std::endl; } }; class Child: public Parent { private: int* m_array; public: Child(int length) { m_array = new int[length]; } ~Child() // примечание: Деструктор не виртуальный { std::cout << "Calling ~Child()" << std::endl; delete[] m_array; } }; int main() { Child *child = new Child(7); Parent *parent = child; delete parent; return 0; } |
Поскольку parent
является указателем класса Parent, то при его уничтожении компилятор будет смотреть, является ли деструктор класса Parent виртуальным. Поскольку это не так, то компилятор вызовет только деструктор класса Parent.
Результат выполнения программы:
Calling ~Parent()
Тем не менее, нам нужно, чтобы delete вызывал деструктор класса Child (который, в свою очередь, будет вызывать деструктор класса Parent), иначе m_array
не будет удален. Это можно выполнить, сделав деструктор класса Parent виртуальным:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream> class Parent { public: virtual ~Parent() // примечание: Деструктор виртуальный { std::cout << "Calling ~Parent()" << std::endl; } }; class Child: public Parent { private: int* m_array; public: Child(int length) { m_array = new int[length]; } virtual ~Child() // примечание: Деструктор виртуальный { std::cout << "Calling ~Child()" << std::endl; delete[] m_array; } }; int main() { Child *child = new Child(7); Parent *parent = child; delete parent; return 0; } |
Результат выполнения программы:
Calling ~Child()
Calling ~Parent()
Правило: При работе с наследованием ваши деструкторы должны быть виртуальными.
Виртуальное присваивание
Оператор присваивания можно сделать виртуальным. Однако, в отличие от деструктора, виртуальное присваивание не всегда является хорошей идеей. Почему? Это уже выходит за рамки этого урока. Следовательно, для сохранения простоты в вашем коде, не рекомендуется использовать виртуальное присваивание.
Игнорирование виртуальных функций
В языке С++ мы можем игнорировать вызов переопределений. Например:
1 2 3 4 5 6 7 8 9 10 11 |
class Parent { public: virtual const char* getName() { return "Parent"; } }; class Child: public Parent { public: virtual const char* getName() { return "Child"; } }; |
Здесь мы хотим, чтобы ссылка класса Parent на объект класса Child вызывала Parent::getName() вместо Child::getName(). Чтобы это сделать, нужно просто использовать оператор разрешения области видимости:
1 2 3 4 5 6 7 8 9 |
#include <iostream> int main() { Child child; Parent &parent = child; // Вызов Parent::GetName() вместо переопределения Child::GetName() std::cout << parent.Parent::getName() << std::endl; } |
Вы, скорее всего, не будете использовать это очень часто, но знать об этом стоит.
Хотелось бы поподробнее узнать, о причинах или получить ссылку на источник, где рассказано об этом, так как уже было с выравниванием данных, а тут просто сказали "это плохо, всё идём дальше", без объяснения причин.
Не совсем понятно зачем делать деструктор класса Child виртуальным.
В данном примере у него нет наследников.
Если тут как с методами, то деструктор класса-наследника будет неявно виртуальным, так как является переопределением виртуального деструктора в классе-родителе, ключевое слово указывается, чтобы не забыть это.
Строго говоря, при удалении наследника при помощи delete с указателем на базовый класс будет неопределённое поведение. То, что вызывается деструктор базового класса — лишь один из вариантов проявления неопределённого поведения.
Есть альтернативное решение, касающееся деструкторов базовых классов — объявить их защищёнными невиртуальными, если базовый класс или интерфейс не предназначен для полиморфного удаления
Тогда на этапе компиляции будет ошибка:
1>error C2248: 'Parent::~Parent': cannot access protected member declared in class 'Parent'
Т.е. это защита от неправильного использования указателя?
Нужно явно вызывать
Классный сайт! Спасибо, что объясняете всё понятно и бесплатно!
Пожалуйста 🙂