Виртуальные функции и полиморфизм

Механизм виртуальных функций в ООП используется для реализации полиморфизма: создания метода, предназначенного для работы с различными объектами из иерархии наследования за счет механизма позднего (динамического) связывания. Виртуальные функции объявляются в базовом и производном классах с ключевым словом 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;

}

Если конструкторы не могут быть виртуальными, деструкторы могут (для полиморфных объектов их рекомендуется объявлять в базовых классах как виртуальные).

Наши рекомендации