class Figure {
public:
virtual double Square() = 0;
virtual void Print() = 0;
virtual ~Figure() {};
};
Что делает эта строка:
virtual ~Figure() {};
Для начало просто запомните одно правило: если у вас в классе наследнике есть хотя одно поле с нетривиальным деструктором, деструктор также следует сделать виртуальным. Давайте же теперь разберемся из-за чего сформулировалось данное правило... Предлагаю взять самый банальный пример, который описывается практически во всех книгах по языку С++:
class Base {
public:
Base () { cout << "Base()" << endl; }
~Base () { cout << "~Base()" << endl; }
};
class Derived : public Base {
public:
Derived () { cout << "Derived()" << endl; }
~Derived () { cout << "~Derived()" << endl; }
};
int main()
{
Derived derived;
return EXIT_SUCCESS;
}
В ходе выполнения данного кода, мы получим следующее:
Base()
Derived()
~Derived()
~Base()
Результат программы получился ожидаемым, за единственным исключением того, что сначала конструируется базовая часть класса, затем производная, а при разрушении наоборот — сначала вызывается деструктор производного класса, который по окончании своей работы вызывает по цепочке деструктор базового. Но, что если нам создать этот объект в динамической памяти, использую при этом указатель на объект базового класса:
int main()
{
Base* base = new Derived;
delete base;
return EXIT_SUCCESS;
}
А вот в этот раз, при выполнении данной программы мы получим совершенно не то, что ожидали, а именно:
Base()
Derived()
~Base()
Здесь также конструируется объект так, как и надо, но вот при разрушении происходит не то что ожидалось, а именно утечка памяти, потому как деструктор производного класса не вызывается. Причина данного поведения в том, что удаление производится через указатель на базовый класс и для вызова деструктора компилятор использует раннее связывание. Деструктор базового класса не может вызвать деструктор производного, потому что он о нем ничего не знает. В итоге часть памяти, выделенная под производный класс, безвозвратно теряется. Думаю у вас уже появился вопрос, на тему того, что же можно сделать, чтобы избежать этого. Ответ на этот вопрос будет очень простым, чтобы избежать данной проблемы, деструктор в базовом классе должен быть виртуальным.
class Base {
public:
Base () { cout << "Base()" << endl; }
virtual ~Base () { cout << "~Base()" << endl; }
};
class Derived : public Base {
public:
Derived () { cout << "Derived()" << endl; }
~Derived () { cout << "~Derived()" << endl; }
};
int main()
{
Base* base = new Derived;
delete base;
return EXIT_SUCCESS;
}
Теперь-то мы наконец получим ожидаемый нами результат:
Base()
Derived()
~Derived()
~Base()
Осталось, разобраться только почему виртуальный деструктор избавил нас от этой проблемы. А все из-за того, что теперь отныне для вызова деструктора используется позднее связывание, то есть при разрушении объекта берется указатель на класс, затем из таблицы виртуальных функций определяется адрес нужного нам деструктора, а это деструктор производного класса, который после своей работы, как и полагается, вызывает деструктор базового. Также, не стоит забывать и о виртуальных функциях в деструкторе, но я уже здесь не буду затрагивать эту тему, так как ответ получится очень большим.
Лирическое отступление: Даже после такого подробного объяснения, у вас скорее всего еще останется много различных вопросов и недопониманий различных тонкостей и нюансов с "виртуальностью" в С++. Но чтобы уже окончательно разложить все по полочкам, советую вам основательно разобраться c моделью размещения объектов, а также о позднем и раннем связывании:
О модели размещения объектов(Часть 1)
О модели размещения объектов(Часть 2)
О позднем и раннем связывании
Надеюсь мой ответ оказался вам полезным, желаю удачи в изучении С++!
Данная запись означает описание деструктора для имени класса Figure
.
Деструктор имеет такое же имя как и класс только со знаком тильда(~).
Про слово virtual в вышенаписанной строке: "Виртуальная функция — это функция, объявленная с ключевым словом virtual в базовом классе и переопределенная в одном или в нескольких производных классах."
В фигурных скобках так же могут быть записаны элементы, которые были созданы с помощью new
тогда, в фигурных скобках необходимо записать delete
и после указание на объект.
Пример вызова деструктора:
class Foo {
public:
Foo(const char *name) { m_len = strlen(name); m_name = new char[m_len+1]; strcpy(name, m_name); m_name[m_len] = '\0'; }
~Foo() { delete [] m_name; }
private:
int m_len;
char *m_name;
};
(пример взят из вики)
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
В задании по С++ сказанно отсортирвать массив названий товаров продуктового магазина по алфавиту на русскомС английской сортировкой в Java мне...
Чтобы внести имя зависимого типа из базового класса в класс-наследник с помощью using declaration, нужно явно указать ключевое слово typenameНо как внести...