Виртуальные методы и полиморфизм
Как взаимодействуют между собой объекты разных классов, связанных отношением наследования? Возможны ли взаимные присваивания между объектами родительских классов и их потомками? Ответ на второй вопрос положителен лишь "наполовину". Совместимость по присваиванию обеспечивается лишь в одну сторону - от родителей к потомкам: объекту родительского класса может быть присвоено значение объекта-потомка - обратное недопустимо. Это и понятно: ведь потомок, сохраняя все свойства родителя, может иметь и другие свойства.
Присваивание объекта объекту производится редко. Важнее, что совместимость по присваиванию имеет место в следующих случаях:
- совместимость между формальным и фактическим параметром процедуры или функции. Формальный параметр может иметь тип базового класса, а фактический параметр - аргумент - при вызове функции может быть объектом любого производного класса.
- совместимость указателей. Указатель может ссылаться на базовый класс, но при конструировании объекта в операторе "new" может быть вызван конструктор производного класса, что позволяет создать объект соответствующего класса. В более общей ситуации указателю базового класса может быть присвоен указатель производного класса.
Ниже мы покажем, что свойство совместимости по присваиванию в сочетании с возможностью определения виртуальных методов приводит к весьма полезным следствиям.
Семейство классов может иметь виртуальные методы. Если какой-то метод базового класса объявлен как виртуальный с атрибутом virtual, эта характеристика сохраняется для всех классов-потомков. В случае переопределения в производном классе виртуального метода должно сохраняться число параметров метода и их типы, что гарантирует одинаковую форму вызова виртуального метода как производного, так и базового класса.
Когда создается объект, в классе которого определены виртуальные методы, конструктор класса для этого объекта заносит строку в таблицу виртуальных методов. В ней содержатся ссылки на расположение виртуальных методов данного класса на этапе выполнения приложения, что позволяет реализовать механизм "позднего связывания" и при вызове объектом своего виртуального метода динамически найти по таблице и вызвать нужный метод.
Виртуальность обеспечивает возможность написания полиморфной функции, в теле которой вызываются виртуальные методы.
Принципиально виртуальный метод от не виртуального отличается механизмом позднего связывания , применяемым для организации вызова виртуального метода. Во многих языках программирования методы могут быть как виртуальными, так и, в большинстве случаев, не виртуальными. В языке VBA это не так, здесь практически все методы виртуальные и для них применяется механизм позднего связывания. Это и понятно, поскольку VBA по существу интерпретируемый язык, а интерпретация подразумевает позднее связывание. Поэтому, чтобы Вы почувствовали разницу между виртуальными и не виртуальными методами, позвольте привести один пример на языке Visual C++. Рассмотрим следующий пример. Пусть имеются базовый класс Base и производный от него класс Derived, в которых определены виртуальный метод VirtMethod и невиртуальный метод NonVirtMethod. В базовом классе определена также полиморфная функция Test:
class Base { public: virtual void VirtMethod (); // печатает слово" Отец" void NonVirtMethod (); // печатает слово" Мать"
void Test () { VirtMethod (); // вызов виртуального метода NonVirtMethod (); // вызов не виртуального метода } }
class Derived: public Base { public: virtual void VirtMethod (); // печатает слово" Сын"
void NonVirtMethod (); // печатает слово" Дочь" }
Когда компилятор в базовом классе Base создает код для функции Test, то, анализируя вызов не виртуального метода NonVirtMethod(), он уже на этапе компиляции создаст обращение к методу NonVirtMethod класса Base.
Совсем иначе он поступит, анализируя вызов виртуального метода VirtMethod. Для виртуального метода на этапе компиляции нельзя определить, метод какого класса следует вызывать. Это может быть метод класса Base или класса Derived. Возможно даже, что уже после того, как функция Test будет откомпилирована, программист определит новый класс - потомок класса Derived со своим виртуальным методом VirtMethod.
Решение о том, метод какого класса следует вызывать, откладывается до момента вызова функции Test. Решение принимается не на этапе компиляции, а на этапе выполнения программы. В момент вызова виртуального метода, используя таблицу виртуальных методов, выполняется необходимое связывание и вызывается требуемый метод соответствующего класса. Закончим наш пример объявлением объектов базового и производного классов и вызовами функции Test с этими объектами в качестве фактических параметров:
// При создании объекта BaseItem вызывается конструктор класса Base. Base BaseItem; // При создании объекта DerivedItem вызывается конструктор класса Derived Derived DerivedItem; // Вызов функции Test BaseItem.Test(); // Будет напечатано "Отец" и "Мать" DerivedItem.Test(); // Будет напечатано "Сын" и "Мать".
Функция Test является примером полиморфной функции. Заметьте: она имеет по умолчанию первым параметром указатель на объект базового класса (this). Продолжим рассмотрение нашего примера и покажем, как указатели обеспечивают полиморфизм:
// Полиморфные указатели pItem1 и pItem2 ссылаются на базовый класс Base. // Вызывается конструктор и создается динамический объект класса Base. Base* pItem1 = new Base(); // Вызывается конструктор и создается динамический объект класса Derived Base* pItem2 = new Derived();
// Будет вызван метод класса Base и напечатано слово "Отец". pItem1 ->VirtMethod(); // Будет вызван метод класса Derived и напечатано слово "Сын". pItem2 -> VirtMethod();
Теперь мы можем уточнить понятие полиморфизма семейства классов. Заметьте, что полиморфизм связан именно с семейством классов. Внутри одного класса могут существовать перегруженные методы, но не полиморфные. Виртуальный метод, определенный в базовом и производных классах с одним и тем же именем и одним и тем же набором параметров, но с разной реализацией, называется полиморфным. Такой метод существует во множестве форм. Функция, имеющая в качестве параметра объект базового класса и вызывающая виртуальные методы этого объекта, также называется полиморфной.Она работает по-разному в зависимости от того, какой объект передан ей в качестве фактического параметра. Полиморфным называется и указатель базового класса, вызывающий виртуальные методы. Полиморфизм такого указателя обеспечивается за счет того, что указатель может быть связан с объектом любого из производных классов. Семейство классов обладает свойством полиморфизма, если в нем реализованы полиморфные функции и полиморфные указатели.