Слово полшюрфизм от греческих слов poly (много) и morphos (форма) означает множественность форм. Полиморфизм - это свойство родственных объектов (т.е. объектов, классы которых являются производными от одного родителя) вести себя по-разному в зависимости от ситуации, возникающей в момент выполнения программы. В рамках ООП программист может влиять на поведение объекта только косвенно, изменяя входящие в него методы и придавая потомкам отсутствующие у родителя специфические свойства.
Для изменения метода необходимо перегрузить его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющие разную кодовую реализацию и, следовательно, придающие объектам разное поведение. Например, в иерархии родственных классов геометрических фигур (точка, прямая линия, квадрат, прямоугольник, окружность, эллипс и т.д.) каждый класс имеет метод Draw, который отвечает за надлежащий отклик на событие с требованием нарисовать эту фигуру.
Благодаря полиморфизму, потомки могут перегружать общие методы родителя с тем, чтобы реагировать специфическим образом на одно и то же событие.
3.5.1 Виртуальные функции
В ООП полиморфизм достигается не только описанным выше механизмом наследования и перегрузки методов родителя, но и виртуализацией, позволяющей родительским функциям обращаться к функциям потомков. Полиморфизм реализуется через архитектуру класса, но полиморфными могут быть только функции-члены.
В C++ полиморфная функция привязывается к одной из возможных одноименных функций только в момент исполнения, когда ей передается конкретный объект класса. Другими словами, вызов функции в исходном тексте программы лишь обозначается, без точного указания на то, какая именно функция вызывается. Такой процесс известен как позднее связывание. Листинг 3.9 показывает, к чему может привести не полиморфное поведение обычных функций-членов.
I class Parent { public:
double Fl(double x) { return x*x; }
double F2(double x) { return Fl(x)/2; }
class Child: public Parent { public:
double Fl(double x) { return x*x*x; } };
void main() {
Child child;
cout “ child.F2(3) “ end1;
}
Листинг 3.9. Неопределенное позднее связывание.
Класс Parent содержит функции-члены Fl и F2, причем F2 вызывает, El,. Класс Child, производный от класса Parent, наследует функцию F2, однако переопределяет функцию Fl. Вместо ожидаемого результата 13.5 программа выдаст значение 4.5. Дело в том, что компилятор оттранслирует выражение child. F2 (3) в обращение к унаследованной функции Parent::F2, которая в свою очередь вызовет Parent::F1, а не Child::F1, что поддержало бы полиморфное поведение.
C++ однозначно определяет позднее связывание в момент выполнения и обеспечивает полиморфное поведение функций посредством их виртуализации. Листинг 3.10 обобщает синтаксис объявления виртуальных функций в базовом и производном классах.
jclass classNamel {
// Другие функции-члены
virtual returnType functionName(<список параметров>);
};
class className2: public classNamel {
// Другие функции-члены
virtual returnType functionName(<cmicoK параметров>);
};
Листинг 3.10. Объявление виртуальных функции в иерархии классов.
Чтобы обеспечить полиморфное поведение функции F1 в объектах классов Parent и Child, необходимо объявить ее виртуальной. Листинг 3.11 содержит модифицированный текст программы.
class Parent {
public:
virtual double F1(double x) { return x*x; }
double F2(double x) { return Fl(x)/2; }
};
class Child: public Parent { public:
virtual double F1(double x) { return x*x*x; }
);
void main() {
Child child;
cout “ child.F2(3) “ endl;
}
Листинг 3.11. Позднее связывание виртуальных функций.
Теперь программа выдаст ожидаемый результат 13.5. Компилятор оттранслирует выражение child. F2 (3) в обращение к унаследованной функции Parent:: F2, которая в свою очередь вызовет переопределенную виртуальную функцию потомка Child::F1.
Если функция объявлена в базовом классе как виртуальная, ее можно переопределять только в производных классах и обязательно с тем же списком параметров. Если виртуальная функция производного класса изменила список параметров, то ее версия в базовом классе (и во всех его предшественниках) станет недоступной. Поначалу такая ситуация может показаться тупиковой - и на деле оказывается таковой в языках ООП, которые не поддерживают механизм перегрузки. C++ решает проблему, допуская использовать не виртуальные, а перегруженные функции с тем же именем, но с другим списком параметров.
Функция, объявленная виртуальной, считается таковой во всех производных классах - независимо от того, объявлена ли она в производных классах с ключевым словом virtual, или нет.
Используйте виртуальные функции для реализации специфического поведения объектов данного класса. Не объявляйте все ваши методы виртуальными - это приведет к дополнительным вычислительным затратам при их вызовах. Всегда объявляйте деструкторы виртуальными. Это обеспечит полиморфное поведение при уничтожении объектов в иерархии классов.