logo
Лекции по С++ глава 4

4.5.1. Применение виртуальных функций для управления объектами классов

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

const int MAXFIGS = 100;

CRectangle *PFigure [MAXFIGS];

int Count = 0;

// ...

// пользователь создает блок:

PFigure [Count++] = new CBlock (10, 15, 25, 30, 5);

// ...

// пользователь создает прямоугольник:

PFigure [Count++] = new CRectangle(5, 6, 19, 23);

// ...

// пользователь создает блок с закругленными углами;

PFigure [Count++] = new CRoundBlock(27, 33, 43, 56, 10, 5);

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

//НЕ РЕКОМЕНДУЕТСЯ:

class CRectangle

{

// другие определения ...

public:

int: Type; // наследуется всеми производными классами;

// хранит код, идентифицирующий класс объекта:

// RECT, BLOCK, либо ROUNDBLOCK

}

В данном примере предполагается, что три символьные константы RECT, BLOCK и ROUNDBLOCK были определены в программе предварительно.

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

// дурной тон; НЕ РЕКОМЕНДУЕТСЯ:

for (int i = 0; i < Count; ++i)

switch (PFigure [i]->Type)

{

case RECT:

PFigure[i]->Draw(); break;

case BLOCK:

((CBlock *)PFigure[i])->Draw(); break;

case ROUNDBLOCK:

((CRoundBlock *)PFigure[i])->Draw(); break;

}

Этот пример не только неуклюж, но и требует добавления новой ветки case в оператор switch при изменении программы для поддержки нового типа фигур (например, при внесении в иерархию класса для создания новой фигуры).

Но, если сделать функцию Draw виртуальной, добавив спецификатор virtual в ее объявление внутри класса CRectangle, программа автоматически вызовет правильную версию функции для текущего типа объекта. Перерисовка фигуры может быть выполнена с помощью следующего фрагмента программы.

for (int i = 0; i < Count; ++i) PFigure [i]->Draw{);

Этот код проще и компактнее. К тому же он не требует изменения при добавлении в иерархию нового класса, поддерживающего фигуры другого типа.

Примечание

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