Зачем нужен виртуальный деструктор в С++

      Комментарии к записи Зачем нужен виртуальный деструктор в С++ отключены

Главная Форумы Программирование Программирование на С++ Учебные материалы по С++ Зачем нужен виртуальный деструктор в С++

В этой теме 1 ответ, 2 участника, последнее обновление  Васильев Владимир Сергеевич 1 год, 2 мес. назад.

  • Автор
    Сообщения
  • #2715

    questioner
    Участник

    Виртуальные функции могут быть переопределены в классах наследниках, но как деструктор может быть виртуальным?

  • #2721

    У каждой функции есть адрес, с которого в памяти располагаются ее команды. При вызове функции этот адрес помещается в регистр программного счетчика процессора, который всегда указывает на текущую выполняемую команду. Это справедливо как для обычных функций, так и для функций-членов классов. Однако вызов виртуальной функции немного отличается, т.к. выбор функции должен происходить в зависимости от динамического типа объекта. Рассмотрим пример:

    #include <iostream>
    #include <vector>
    #include <string>
    
    class Profession {
    public:
      virtual void work() {
         std::cout << "do not work" << std::endl;
      }
    };
    
    class Programmer: public Profession {
      std::string m_code;
    public:
      Programmer() {
        m_code = "programmer writes code";
      }
      virtual void work() {
        std::cout << m_code << std::endl;
      }
    };
    
    class Pilot: public Profession {
    public:
      virtual void work() {
        std::cout << "pilot flying" << std::endl;
      }
    };
    
    int main() {
      std::vector<Profession*> workers; 
      Pilot pilot; 
    
      workers.push_back(new Pilot());
      workers.push_back(new Programmer());
    
      for (Profession* worker : workers) {
        worker->work();
      }
    
      pilot.work();
      
      for (Profession* worker : workers) {
        delete worker;
      }
    }

    В данном примере создается список профессий, в который помещается один пилот и один программист. У каждого из них вызывается виртуальная функция work(). Вызов функции у нас происходит через указатель на базовый класс, статический тип объекта — Profession, поэтому если бы функция была не виртуальной — сработала бы функция базового класса. Однако из-за того, что функция виртуальная — используется динамический тип объекта (Programmer и Pilot соответственно).

    Такой эффект достигается за счет использования таблицы виртуальных функций, которая есть в каждом объекте, содержащем хоть одну виртуальную функцию. В этой таблице хранятся адреса функций, которые помещаются в таблицу конструктором и используются при вызове.

    Кроме того, в примере создается объект pilot (на стеке, как и vector). Деструктор для него будет вызван по окончанию работы функции main(). Деструктор дочернего класса освобождает память из своих данных-членов и всегда вызывает деструктор базового класса. Таким образом, в данном случае будет вызван сначала ~Pilot(), а затем ~Profession().

    В рассмотренном примере есть проблема, связанная с тем, что компилятор автоматически создаст в классе невиртуальный деструктор. В связи с этим, при разрушении объекта будет вызван деструктор, соответствующий статическому типу объекта — для обоих рабочих вызовется ~Profession(). Однако, в классе Programmer есть поле, в которое помещается строка — его деструктор базового класса удалять не будет. Убедиться в этом можно с помощью valgrind memcheck:

    virtual_destructor_valgrind

    Чтобы решить проблему достаточно сделать деструктор базового класса виртуальным — в этом случае при разрешении объекта правильная реализация деструктора будет выбираться из таблицы виртуальных функций, т.е. использоваться динамический тип объекта:

      virtual ~Profession() {
      }

Для ответа в этой теме необходимо авторизоваться.