Опубликован: 03.09.2010 | Уровень: для всех | Доступ: свободно
Лекция 7:

Иерархии объектов. Работа с объектами в динамической памяти

< Лекция 6 || Лекция 7: 12345

Позднее связывание. Виртуальные методы

При раннем связывании программа, готовая для выполнения, представляет собой структуру, логика выполнения которой жестко определена. Если же требуется, чтобы решение о том, какой из одноименных методов разных объектов иерархии использовать, принималось в зависимости от конкретного объекта, для которого выполняется вызов, то ясно, что заранее связывать жестко эти методы с остальной частью кода нельзя. Следовательно, надо каким-то образом дать знать компилятору, что эти методы будут обрабатываться по-другому. Для этого в Паскале существует директива virtual. Она записывается в заголовке метода, например:

procedure attack; virtual;

Слово virtual в переводе с английского значит "фактический". Объявление метода виртуальным означает, что все ссылки на этот метод будут разрешаться по факту его вызова, то есть не на стадии компиляции, а во время выполнения программы. Этот механизм называется поздним связыванием. Для его реализации необходимо, чтобы адреса виртуальных методов хранились там, где ими можно будет в любой момент воспользоваться, поэтому компилятор формирует для этих методов таблицу виртуальных методов (VMTvirtual method table). В первое поле этой таблицы записывается размер объекта, а затем идут адреса виртуальных методов (в том числе и унаследованных) в порядке описания в объекте. Для каждого объектного типа создается одна VMT.

Каждый объект во время выполнения программы должен иметь доступ к VMT. Обеспечение этой связи нельзя поручить компилятору, так как она должна устанавливаться позже — при создании объекта во время выполнения программы. Поэтому связь экземпляра объекта с VMT устанавливается с помощью специального метода, называемого конструктором.

Класс, имеющий хотя бы один виртуальный метод, должен содержать конструктор:

type monster = object
        constructor init(x_, y_, health_, ammo_ : word);
        procedure attack; virtual;
        procedure draw;   virtual;
        procedure erase;  virtual;
        procedure hit;
        procedure move(x_, y_ : word);
    private
        x, y         : word;
        health, ammo : word;
        color        : word;
    end;
    daemon = object (monster)
        constructor init(x_, y_, health_, ammo_, magic_ : word);
        procedure attack; virtual;
        procedure draw;   virtual;
        procedure erase;  virtual;
        procedure wizardry;
    private
        magic: word;
    end;

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

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

Объект может содержать несколько конструкторов. Повторный вызов конструктора вреда программе не наносит, а вот если конструктор вообще не вызвать и попытаться использовать виртуальный метод, поведение программы не определено. Конструктор должен быть вызван для каждого создаваемого объекта. Присваивание одного объекта другому возможно только после конструирования обоих.

Вызов виртуального метода выполняется так: из объекта берется адрес его VMT, из VMT выбирается адрес метода, а затем управление передается этому методу ( рис. 7.2). Таким образом, при использовании виртуальных методов из всех одноименных методов иерархии всегда выбирается тот, который соответствует фактическому типу вызвавшего его объекта.

 Позднее связывание

Рис. 7.2. Позднее связывание

Поскольку связь с VMT устанавливается в самом начале конструктора, в его теле также можно пользоваться виртуальными методами.

Правила описания виртуальных методов:

  • Если в объекте метод определен как виртуальный, во всех потомках он также должен быть виртуальным. Отсутствие ключевого слова virtual в заголовке метода потомка приведет к ошибке.
  • Заголовки всех одноименных виртуальных методов должны совпадать (параметры должны иметь одинаковый тип, количество и порядок следования, функции должны возвращать значение одного и того же типа).
  • Переопределять виртуальный метод в каждом из потомков не обязательно: если он выполняет устраивающие потомка действия, он будет унаследован.
  • Объект, имеющий хотя бы один виртуальный метод, должен содержать конструктор.

Для иллюстрации работы виртуальных методов используем программу из предыдущей лекции, сменив в ней тип элементов массива с monster на daemon ( пример 7.2). Предварительно в модуле monsters методы attack, draw и erase объявим как виртуальные, а в процедурах init заменим ключевое слово procedure на слово constructor.

program game_2;
uses graph, crt, monsters;
const n = 30;
var stado  : array [1 .. n] of daemon;
    x, y   : array [1 .. n] of integer;
    gd, gm : integer;
    i, j   : word;
begin
    gd := detect; initgraph(gd, gm, '...');
    if graphresult <> grOk then begin
        writeln('ошибка инициализации графики'); exit end;
    randomize;
    for i := 1 to n do begin
        stado[i].init(random(600), random(440), random(10), random(8), random(6));
        stado[i].draw;
    end;
    repeat
        for i := 1 to n do begin
            x[i] := random(600); y[i] := random(440);
            stado[i].move(x[i], y[i]);
        end;
        for i := 1 to n – 1 do
            for j := i + 1 to n do
                if (abs(x[i] – x[j]) < 15) and (abs(y[i] – y[j]) < 15)
                then begin
                    stado[i].hit; stado[j].hit;
                end;
        delay(200);
    until keypressed;
end.
Листинг 7.2. Пример использования виртуальных методов

Единственное изменение, которое пришлось сделать в части исполняемых операторов программы, — добавление еще одного параметра в метод инициализации init. Запустив программу, можно наблюдать процесс самоуничтожения демонов, что свидетельствует о том, что теперь из методов move и hit, унаследованных из базового класса, вызываются методы attack, draw и erase, определенные в производном классе.

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

При описании классов рекомендуется определять как виртуальные те методы, которые в производных классах будут реализовываться по-другому. Если во всех классах иерархии метод будет выполняться одинаково или если в потомках он не потребуется, его лучше определить как статический (обычный). Применение виртуальных методов обеспечивает гибкость и возможность расширения функциональности модуля, но несколько замедляет выполнение программы, поскольку эти методы вызываются через обращение к VMT, а не непосредственно.

< Лекция 6 || Лекция 7: 12345
София Шишова
София Шишова

Я завершила экзамен 90 баллов на 5. Сертификат не заказала. Сейчас пытаюсь найти как его заказать. у меня указано экзамен пройден баллы оценка видно, а чтоб заказать сертификат нигде не видно.