|
|
|||||||||||||||||||||||||||||
|
Виртуальные функции. Что это такое? Часть 3 (исходники)Источник: Весельчак У Сергей Малышев (aka Михалыч)
Виртуальные деструкторыЭтой частью мы завершим начатое в первой и второй частях статьи рассмотрение использования виртуальных функций. Если вы читали серию статей "Классы. Копирование и присваивание", то помните, что там говорилось о том, как создаются объекты классов. Теперь поговорим о том, как они уничтожаются. Правда, следует оговориться сразу. Очень неплохо если вы уже знаете, что такое деструкторы в целом. Поскольку эта теоретическая часть сюда никак не вписывается. Если вы не знакомы с этим, совсем не помешает для начала почитать что-либо о конструкторах и деструкторах классов. Когда нужны виртуальные деструкторы?Давайте начнем изучение вопроса с рассмотрения простого (и ставшего уже классическим) примера. Соорудим некий класс, который может запоминать строковое значение. И пусть он у нас будет базовым классом (правда не абстрактным, так как это не важно в данном случае), из него мы будем выводить другие.
Код: class Base Все просто. Конструктор класса выделяет память для строки путем обращения к библиотечной функции strdup и сохраняет адрес новой строки в указателе sp1. Деструктор класса освобождает эту память, когда объект класса Base выходит из области видимости. Код: class Derived: public Base Этот класс сохраняет вторую строку, на которую ссылается его указатель sp2. Новый конструктор вызывает конструктор базового класса, передавая строку в базовый класс, а также выделяет память под вторую строку и сохраняет адрес новой строки в указателе sp2. Деструктор этого класса освобождает эту память. Код: Derived MyStrings(string 1, string 2); Когда этот объект выйдет из области видимости, сначала вызовется деструктор класса Derived, а затем деструктор базового класса Base. Вся память будет аккуратно освобождена. Все по теории, все красиво. Рассмотрим другой вариант. Предположим, что мы объявили указатель на базовый класс Base, но присвоили ему адрес объекта класса Derived. Это вполне допустимо, мы уже обсуждали этот вопрос в предыдущих частях. То есть, это будет выглядеть в программе так: Код: Base *pBase; //указатель на базовый класс Что же произойдет, когда в программе будет удален объект, на который ссылается указатель pBase? Код: delete pBase; //????????????? Компилятор видит, что указатель pBase должен ссылаться на объекты класса Base (откуда бы ему узнать, что именно присвоено этому указателю?). И вполне естественно программа вызовет только деструктор базового класса, и он удалит одну строку, но оставит в памяти другую. Ведь деструктор класса Derived не вызывался! Получается классическая утечка памяти! Вот когда нужен виртуальный деструктор! Код: virtual ~Base() { delete sp1; } //деструктор Что же произойдет в этом случае? Поскольку деструкторы объявлены виртуальными, то их вызовы будут компоноваться уже во время выполнения программы. То есть, объекты сами будут определять, какой деструктор нужно вызвать. Поскольку наш указатель pBase на самом деле ссылается на объект класса Derived, то деструктор этого класса будет вызван, так же как и деструктор базового класса. Деструктор базового класса автоматически выполняется после деструктора производного класса. Ну и, пожалуй, последнее. Код: class Something //абстрактный класс без виртуальных функций Этот класс абстрактный, потому что включает в себя чистую виртуальную функцию (деструктор). Поскольку деструктор виртуальный, то проблемы с вызовом деструктора в будущем возникнуть не должны. Все, что осталось сделать это дать определение этого деструктора. Код: Something::~Something() {}; Это необходимо сделать, поскольку виртуальный деструктор работает таким образом, что вначале вызывается деструктор производного класса, а затем последовательно деструкторы классов, находящихся выше в цепи наследования, вплоть до базового абстрактного. Это означает, что компилятор будет генерировать вызов ~Something(), даже когда класс является абстрактным, поэтому тело функции надо определять обязательно. Если этого не сделать, компоновщик просто выдаст ошибку отсутствия символа. И сделать это все равно придется. В завершение сказанного парочка советов: Вот, собственно, и все, что я хотел вам сказать о виртуальных функциях.
|
|