#include
using namespace std;
class Fish
{
public:
virtual void Swim()
{
cout « "Fish swims!" « endl;
}
10: }; /
1 1 :
class Tuna:public Fish
{
public:
// переопределение Fish::Swim
void Swim()
{
cout « "Tuna swims!" « endl;
}
20: };
2 1 :
class Carp:public Fish
{
public:
// переопределение Fish::Swim
void Swim()
{
cout « "Carp swims!" « endl;
}
};
31:
void MakeFishSwim(Fish& InputFish)
{
// вызов виртуального метода Swim() InputFish.Swim();
}
int main()
59 {
Tuna myDinner;
Carp myLunch;
// передача Tuna как Fish
MakeFishSwim(myDinner);
// передача Carp как Fish
MakeFishSwim(myLunch);
return 0;
}
Результат
Tuna swims!
Carp swims!
Анализ
Реализация функции M ak eF ish S w im (F ish & ) никак не изменилась с листинга 11.1,
вывод получился совсем иной. Метод F i s h : : Swim () не был вызван вообще из-за при сутствия переопределенных версий T u n a : : Swim () и C a r p : : Swim (), которые получили преимущество над методом F i s h : : Swim (), поскольку последний был объявлен как вир туальная функция. Это очень важный момент. Он свидетельствует, что, даже не зная точ ный тип обрабатываемого объекта, класс которого происходит от класса F is h , реализация метода M akeFishSw im () способна привести к вызову разных реализаций метода Swim (), определенного в различных производных классах.
Это полиморфизм: обработка различных рыб как общего типа F is h , при гарантии выполнения правильной реализации метода Swim (), предоставляемого производными типами.
Потребность в виртуальных деструкторах
средств, представленных в листинге 11.1, есть и оборотная сторона: неумышленный вызов функций базового класса из экземпляра производного, когда доступна специализа ция. Что будет, если функция применит оператор d e l e te , используя указатель типа B ase*, который фактически указывает на экземпляр производного класса?
Какой деструктор будет вызван? Рассмотрим листинг 11.3.
ЛИСТИНГ 1 1 .3 . Функция, вызываю щ ая оператор delete для типа Base*
#include
using namespace std;
2 :
282 ЗАНЯТИЕ 11. Полиморфизм
class Fish
{
public:
Fish()
{
cout « "Constructed Fish" « endl;
}
~Fish()
11: {
cout « "Destroyed Fish" « endl;
}
};
15:
class Tuna:public Fish
{
public:
TunaO
20: {
cout « "Constructed Tuna" « endl;
}
-TunaO
{
cout « "Destroyed Tuna" « endl;
}
};
28:
void DeleteFishMemory(Fish* pFish)
{
delete pFish;
}
33:
int main()
{
cout « "Allocating a Tuna on the free store:" « endl;
Tuna* pTuna = new Tuna;
cout « "Deleting the Tuna: " « endl;
DeleteFishMemory(pTuna);
40:
cout « "Instantiating a Tuna on the stack:" « endl;
Tuna myDinner;
cout « "Automatic destruction as it goes out of scope:
endl;
44:
return 0;
}
Результат
Allocating a Tuna on the free store:
Constructed Fish
Constructed Tuna
Deleting the Tuna:
Destroyed Fish
Instantiating a Tuna on the stack:
Constructed Fish
Constructed Tuna
Automatic destruction as it goes out of scope:
Destroyed Tuna
Destroyed Fish
Анализ
Функция m ain () создает экземпляр класса Tuna в динамической памяти, используя оператор new в строке 37, а затем сразу освобождает зарезервированную память, исполь зуя вспомогательную функцию D eleteF ish M em o ry () в строке 39. Для сравнения: другой экземпляр класса Tuna создается в стеке как локальная переменная m yD inner (строка 42)
выходит из области видимости по завершении функции m ain (). Вывод создают опера торы c o u t в конструкторах и деструкторах классов F is h и Tuna. Обратите внимание: не смотря на то, что обе части, Tuna и F is h , объекта были созданы в динамической памяти, поскольку использовался оператор new, при удалении был вызван только деструктор части F ish , а не класса Tuna. Это находится в абсолютном контрасте с созданием и удалением локального объекта m yD inner, где вызываются все конструкторы и деструкторы . В ли стинге 10.7 был представлен правильный порядок создания и удаления классов в иерархии наследования, демонстрирующий, что должны быть вызваны все деструкторы, включая деструктор -T u n a (). Здесь явно что-то неправильно.
Проблема в том, что код в деструкторе производного класса, объект которого был соз дан в динамической памяти при помощи оператора new, не будет вызван, если будет при менен оператор d e l e t e для указателя типа B ase* . В результате ресурсы не будут осво бождены, произойдет утечка памяти и другие ненужные неприятности.
Чтобы избежать этой проблемы, используйте виртуальные деструкторы, как показано
листинге 11.4.
ЛИСТИНГ 1 1 .4 . Использование виртуальных деструкторов для гарантии вы зова деструкторов производных классов при удалении указателя типа Base*___________________
#include
using namespace std;
2 :
class Fish
{
public:
Fish()
■>: {
cout « "Constructed Fish" « endl;
}
virtual -Fish() // виртуальный деструктор!
{
cout « "Destroyed Fish" « endl;
}
};
15:
15: class Tuna:public Fish
{
public:
TunaO
{
284 ЗАНЯТИЕ 11. Полиморфизм
cout << "Constructed Tuna" « endl;
22:}
~Tuna()
24: {
cout « "Destroyed Tuna" « endl;
}
};
28:
void DeleteFishMemory(Fish* pFish)
{
delete pFish;
}
33:
int main()
{
cout « "Allocating a Tuna on the free store:" « e n d l ;
Tuna* pTuna = new Tuna;
cout « "Deleting the Tuna: " « endl;
DeleteFishMemory(pTuna);
40:
cout « "Instantiating a Tuna on the stack:" « endl;
Tuna myDinner;
cout « "Automatic destruction as it goes out of scope:
endl;
44:
return 0;
}
Результат
Allocating a Tuna on the free store:
Constructed Fish
Constructed Tuna
Deleting the Tuna:
Destroyed Tuna
Destroyed Fish
Instantiating a Tuna on the stack:
Do'stlaringiz bilan baham: |