Виртуални функции и полиморфизъм

С помощта на виртуални функции и полиморфизъм, могат да се разработват и реализират програми за обобщена обработка на обекти от всички съществуващи в йерархията класове, като обекти от базовия клас. Ако по време на разработката на такава програма трябва да се добави нов клас в йерархията, това може да стане с незначителни изменения на самата програма.

Един от начините за специфична обработка на обекти от различни класове е използването на оператор switch. Във връзка с това, обаче, възникват различни проблеми – например липса на проверка за типовете, на проверка за всички възможни случаи. Освен това, добавянето или изключването на клас води до промяна на съответния оператор switch. От друга страна, програмистът може да използва механизма на виртуалните функции за автоматично изпълнение на логиката на оператора switch, избягвайки горепосочените проблеми и неудобства. Използването на виртуални функции и полиморфизъм води до създаване на програми, които имат по-малко логически разклонения и по този начин се улеснява проверката и съпровождането на тези програми.

Да предположим, че редицата класове за форми, такива като Circle, Triangle, Square и т.н. са производни от базовия клас Shape. Всеки клас може да има своя собствена функция Draw. В този случай е удобно при рисуване на всяка форма да можем да извикваме функцията Draw за базовия клас Shape и тогава програмата динамично, т.е. по време на своето изпълнение, да определи коя от функциите Draw на съответния производен клас да се изпълни. За да се създаде такава възможност, трябва функцията Draw да се декларира като виртуална в базовия клас и след това да се предефинира във всеки от производните класове, така че да рисува съответната форма.

Една функция се декларира като виртуална с помощта на ключовата дума virtual, записана преди прототипа на функцията в базовия клас. Например в базовия клас можем да запишем:

  1. virtual void Draw () const;
Ако една функция веднъж е декларирана като виртуална, то тя остава виртуална на всяко по-ниско ниво в йерархията. По този начин ако производният клас няма собствена реализация на виртуалната функция, то се използва реализацията, описана в базовия клас. Ако функцията Draw от базовия клас е декларираната като virtual и ако след това тя се извика чрез указател от базовия клас, сочещ към обект от производния клас, например
  1. shapePtr -> Draw ();
то програмата динамично, т.е. по време на изпълнение, ще избере съответната функция от производния клас, това се нарича динамично свързване.

Предефинирата виртуална функция в производния клас трябва да има същия тип на резултата и същата сигнатура, както виртуалната функция в дефиницията в базовия клас, в противен случай се дава съобщение за синтактична грешка.

Ако виртуална функция се извиква чрез обръщение по име и при това се използва операцията за достъп до елемент ‘.’, например
  1. squareObject.Draw();
то извикването се обработва по време на компилация и това се нарича статично свързване.

Полиморфизмът е възможност за обектите от различни класове, свързани с помощта на наследяване, да реагират по различен начин при обръщение към една и съща функция елемент. Полиморфизмът се реализира с помощта на виртуални функции.

Понякога функция елемент, която не е дефинирана като виртуална в базовия клас се предефинира в производния клас. Ако такава функция елемент е извикана чрез указател от базовия клас, то се използва версията на функцията в базовия клас, ако е извикана чрез указател от производния клас, то се използва версията на функцията от производния клас. Това не е полиморфно поведение.
например:
имаме базов клас Employee и производен клас HourlyWorker
  1. HourlyWorker h, *hPtr = &h; //дефинираме обект и указател от
  2.        //производния клас;
  3. Employee *ePtr = &h; //дефинираме указател от базовия клас
Ще напомним, че функцията елемент print е невиртуална, дефинирана в базовия клас и предефинирана в производния клас.
  1. hPtr -> print (); //извиква се функцията елемент print от производния
  2.      //клас
  3. ePtr -> print (); //извиква се функцията елемент print от базовия клас
Причината, че това не е полиморфно поведение – функциите не са виртуални и имат еднаква сигнатура. Ако функцията print беше дефинирана като virtual в базовия клас, то при обръщението
  1. ePtr -> print ();
щеше да се извика функцията елемент print от производния клас.

По време на компилация не е необходимо да се знае типа на обекта, за да се генерира извикване на виртуална функция. По време на изпълнението на програмата, чрез динамично свързване, извикването на виртуална функция елемент се преобразува в извикване на вариант на виртуалната функция от съответния клас. За целта се използва таблица на виртуалните методи. Всеки клас, който съдържа виртуални функции има своя таблица на виртуалните методи.  За всяка виртуална функция елемент от класа, таблицата има елемент, съдържащ указател към вариант на виртуалната функция, който се използва с обектите от дадения клас. Всеки обект от клас, който съдържа виртуални функции, има указател към таблицата на виртуалните методи на този клас. При динамичното свързване, чрез този указател се отива в таблицата на виртуалните методи и там се намира нужният указател към вариант на виртуалната функция от съответния клас.