Ответ в теме: Полиморфизм C++. Основы объектно-ориентированного программирования

#2865

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

cplusplus_polymorphism_example

В этом проекте используется графическая сцена (QGraphicsScene), отображающая все игровые элементы (ежика, тучки и т.п.). На самом деле, все эти графические элементы являются наследниками класса QGraphicsPixmapItem, представляющего собой картинку на графической сцене. Стандартная графическая сцена ничего не знает о наших ежиках, но корректно их отображает за счет использования виртуальных функций. Например, в классе AnimatedGraphicsItem, отвечающего за отображение анимированного графического элемента, реализуется виртуальная функция boundingRect класса QGraphicsItem – за счет этого графическая сцена узнает размер элементов. Внутри графической сцены в каком-либо виде хранится набор указателей на QGraphicsItem (они добавляются функцией QGraphicsScene::addItem(QGraphicsItem * item)), при этом сцена может вызвать, например, код из класса WallCloud за счет использования механизма виртуальных функций и позднего связывания:

QGraphicsScene scene;
QGraphicsItem *item;
if (condition) {
  item = new WallCloud();
}
else {
  item = new StrongCloud();
}

scene.addItem(item);

Статический тип объекта (известный во время компиляции) – QGraphicsItem, а динамический (реальный тип, известный лишь во время выполнения) – WallCloud или StrongCloud. При вызове невиртуальной функции по указателю item будет выбрана функция, соответствующая статическому типу объекта (раннее связывание, на этапе компиляции) , но при вызове виртуальной функции – динамическому (позднее связывание, на этапе выполнения).

Позднее связывание реализуется посредством таблицы виртуальных функций, которые хранятся внутри каждого объекта. Таблица заполняется во время создания объекта в конструкторе – в нее заносятся адреса виртуальных функций. Затем, при вызове функции, не просто переходит переход по адресу функции (как при раннем связывании), а нужный адрес сначала выбирается из таблицы функций.

В этом же примере класс GameWidget содержит внутри себя двумерный массив тучек (vector<vector<CloudElement* >>). На самом деле внутри массива будут экземпляры классов WallCloud, Cloud и StrongCloud, т.е. опять используется полиморфизм. Что это дает?

  1. внутри одного массива мы можем хранить реальные (динамические) типы которых отличаются за счет того, что их статический тип одинаков (все они имеют один базовый класс);
  2. базовый класс задает интерфейс, через который мы можем работать с объектами. Заметьте, что мы могли бы хранить массив указателей на GameElement (т.к. он является базовым для всех CloudElement) или даже QGraphicsItem. Однако, наши тучки должны уметь, например, взрываться (виртуальная функция bang()), мы не сможем вызывать эту функцию, имея указатель на QGraphicsItem, т.к. он не содержит такой функции. Поэтому мы объявляем специальный класс, задающий интерфейс и порождаем от него конкретные виды объектов, реализующих интерфейс, а затем работаем с объектами через указатель на класс интерфейса.