1
class Figure {
public:
    virtual double Square() = 0;
    virtual void   Print() = 0;
    virtual ~Figure() {};
};

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

virtual ~Figure() {};
  • 1
    объявляет и определяет ничего не делающий виртуальный деструктор, причем ; в конце лишняя – user7860670 Dec 02 '18 at 11:44
  • @VTT А для чего "~"? – Alrott SlimRG Dec 02 '18 at 11:45
  • Это запись для деструктора ~ИмяКласса, а без ~ был бы конструктор – user7860670 Dec 02 '18 at 11:46
  • ААаааа - понял, спасибо – Alrott SlimRG Dec 02 '18 at 12:03
  • 1
    Вопрос про виртуальный деструктор - один из самых распространённых на собеседованиях. – Alexander Petrov Dec 02 '18 at 14:34
  • @AlrottSlimRG, обрати внимание на эти вопросы: 1. https://ru.stackoverflow.com/q/764922/178988 2. https://ru.stackoverflow.com/q/764696/178988 3. https://ru.stackoverflow.com/q/764692/178988 – Qwertiy Dec 02 '18 at 14:42

2 Answers2

4

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

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)

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

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

  • Не учел этого, спасибо за замечание, сейчас исправлю) – VladimirBalun Dec 02 '18 at 14:34
  • 1
    @pavel, вроде бы пришли к выводу, что всегда надо, если объект будет уничтожаться через указатель на родительский тип: 1. https://ru.stackoverflow.com/q/764922/178988 2. https://ru.stackoverflow.com/q/764696/178988 3. https://ru.stackoverflow.com/q/764692/178988 – Qwertiy Dec 02 '18 at 14:40
  • @Qwertiy спасибо. похоже я тут был неправ. – pavel Dec 02 '18 at 14:43
0

Данная запись означает описание деструктора для имени класса 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;
};

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