Виртуальные функции и полиморфизм
Механизм виртуальных функций в ООП используется для реализации полиморфизма: создания метода, предназначенного для работы с различными объектами из иерархии наследования за счет механизма позднего (динамического) связывания. Виртуальные функции объявляются в базовом и производном классах с ключевым словом virtual, имеют одинаковое имя, список аргументов и возвращаемое значение. Метод, объявленный в базовом классе как виртуальный, будет виртуальным во всех производных классах, где он встретится, даже если слово virtual отсутствует.
В отличие от механизма перегрузки функций (функции с одним и тем же именем, но с различными типами аргументов считаются разными) виртуальные функции объявляются в порожденных классах с тем же именем, возвращаемым значением и типом аргументов. Если различны типы аргументов, виртуальный механизм игнорируется. Тип возвращаемого значения переопределить нельзя.
Основная идея в использовании виртуальных функций состоит в следующем: такая функция может быть объявлена в базовом классе, а затем переопределена в каждом производном классе. Каждый объект класса, управляемого из базового класса с виртуальными функциями, содержит указатель на таблицу vmtbl(virtual method table) с адресами виртуальных функций. Эти адреса устанавливаются в адреса нужных для данного объекта функций во время выполнения. При этом доступ через указатель на объект базового класса осуществляется к виртуальной функции из базового класса, а доступ через указатель на объект производного класса – на функцию из этого же класса. То же происходит при передаче функции объекта производного класса, если аргумент объявлен как базовый класс.
Оба варианта рассмотрены в следующем примере:
//пример 33
/*здесь указатель на базовый класс и ссылка используются для доступа к виртуальной функции */
#include <iostream.h>
#include <conio.h>
class Base {
public:
virtual void who() { // определение виртуальной функции
cout << "Base\n";
}
};
class First : public Base {
public:
void who() { // определение who() относительно First
cout << "First\n";
}
};
class Second : public Base {
public:
void who() { // определение who() относительно Second
cout << "Second\n";
}
};
/* параметр ссылкa на объект базового класса */
void show_who(Base &r) {
r.who();
}
int main() {
Base base_obj;
Base* pb;
pb=&base_obj;
pb->who(); //base_obj.who()
First first_obj;
pb=&first_obj;
pb->who(); //first_obj.who()
Second second_obj;
pb=&second_obj ;
pb->who(); //second_obj.who()
show_who(base_obj); // доступ к Base's who()
show_who(first_obj); // доступ к First's who()
show_who(second_obj); // доступ к Second's who()
while(!kbhit());
return 0;
}
Вывод: Base
First
Second
Base
First
Second
В следующем примере рассматривается реализация стека и очереди на основе односвязного списка, при этом очередь и стек являются потомками класса List:
/*классы, наследование и виртуальные функции
создание класса родовой список для целых*/
#include <iostream.h>//пример 34
#include <stdlib.h>
class List {
public:
List *head; //указатель на начало списка
List *tail;//указатель на конец списка
List *next;//указатель на следующий элемент
int num; //число для хранения
List () { head = tail = next = NULL; }
virtual void store(int i) = 0;/*абстрактная базовая виртуальная функция*/
virtual int retrieve() = 0;/*абстрактная базовая виртуальная функция*/
};
//создание типа очередь на базе списка
class Queue : public List {
public:
void store(int i);
int retrieve();
queue operator+(int i) { store(i); return *this; }
/* перегрузка постфиксного инкремента */
int operator --(int unused) { return retrieve(); } };
void Queue::store(int i) {
list *item;
item = new Queue;
if(!item) {
cout << "ошибка выделения памяти\n";
exit(1); }
item -> num = i;
//добавление в конец списка
if(tail) tail -> next = item;
tail = item;
item -> next = NULL;
if(!head) head = tail;
}
int Queue::retrieve() {
int i;
List *p;
if(!head) {cout << "список пуст\n";return 0; }
//удаление из начала списка
i = head -> num;
p = head;
head = head -> next;
delete p;
return i;
}
class Stack : public List {/*создание класса стек на базе списка */
public:
void store(int i);
int retrieve();
stack operator+(int i) { store(i); return *this; }
int operator --(int unused) { return retrieve(); }
};
void stack::store(int i) {
List *item;
item = new Stack;
if(!item) {
cout << "ошибка выделения памяти\n";
exit(1);}
item -> num = i;
//добавление в начало списка, как в стеке
if(head) item -> next = head;
head = item;
if(!tail) tail = head;
}
int Stack::retrieve() {
int i;
list *p;
if(!head) {cout << "список пуст\n";
return 0; }
//удаление из начала списка
i = head -> num;
p = head;
head = head -> next;
delete p;
return i;
}
int main() {
List *p;
//демонстрация очереди
Queue q_ob;
p = &q_ob; //указывает на очередь
q_ob + 1;
q_ob + 2;
q_ob + 3;
cout << "очередь : ";
cout << q_ob --;// вывод 1
cout << q_ob --;// вывод 2
cout << q_ob --;// вывод 3
cout << '\n';
//демострация стека
Stack s_ob;
p = &s_ob; //указывает на стек
s_ob + 1;
s_ob + 2;
s_ob + 3;
cout << "стек: ";
cout << s_ob --;// вывод 3
cout << s_ob --;// вывод 2
cout << s_ob --;// вывод 1
cout << '\n';
return 0;
}
Если конструкторы не могут быть виртуальными, деструкторы могут (для полиморфных объектов их рекомендуется объявлять в базовых классах как виртуальные).