Что делает строка virtual ~Figure() {};?

116
24 мая 2019, 14:30
class Figure {
public:
    virtual double Square() = 0;
    virtual void   Print() = 0;
    virtual ~Figure() {};
};

Что делает эта строка:

virtual ~Figure() {};
Answer 1

Для начало просто запомните одно правило: если у вас в классе наследнике есть хотя одно поле с нетривиальным деструктором, деструктор также следует сделать виртуальным. Давайте же теперь разберемся из-за чего сформулировалось данное правило... Предлагаю взять самый банальный пример, который описывается практически во всех книгах по языку С++:

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)

О позднем и раннем связывании

Надеюсь мой ответ оказался вам полезным, желаю удачи в изучении С++!

Answer 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;
};

(пример взят из вики)

READ ALSO
Сортировка по русскому алфавиту С++

Сортировка по русскому алфавиту С++

В задании по С++ сказанно отсортирвать массив названий товаров продуктового магазина по алфавиту на русскомС английской сортировкой в Java мне...

136
Перегрузка операции ==

Перегрузка операции ==

Есть класс и у него перегружена операция ==

132
Как внести имя шаблона в класс с помощью using declaration?

Как внести имя шаблона в класс с помощью using declaration?

Чтобы внести имя зависимого типа из базового класса в класс-наследник с помощью using declaration, нужно явно указать ключевое слово typenameНо как внести...

131
Зачем super в методах? Android

Зачем super в методах? Android

Часто встречаю код, примерно как этот:

119