Виртуальные элементы-функции

Перегрузка функций

Перегрузка функции - это использование одинакового имени для функций, выполняющих похожие действия, но с разными типами данных и объявленных в одной области действия.

Классическим примером перегрузки функций является наличие нескольких конструкторов в классе.

Перегруженные функции имеют следующие свойства:

- должны отличаться сигнатурой, т.е. числом, порядком следования и типами параметров; тип возвращаемого значения и имена параметров не учитываются; пример перегруженной функции vvod:

void vvod(int m, int n) {...}

void vvod(float m, float n) {...}

void vvod(float m) {...}

void vvod(char ch) {...}

- должны быть объявлены в одной области действия, например, класса, файла; элементы-функции разных классов не являются перегруженными, так как имеют свою область действия класс.

- выбор соответствующей функции производится компилятором в результате сравнения оператора вызова с сигнатурами функций согласно алгоритму выбора.

Алгоритм выбора функции состоит из следующих этапов:

- проверка на точное соответствие;

- проверка на стандартные преобразования типов;

- проверка на преобразования, определяемые классом (см. выше - в примерах на преобразование из float в complex и из complex в float эти типы становятся совместимыми).

Примеры:

int x, y; vvod(x, y); //вызов vvod(int, int), точное соответствие

int x; vvod(x); //вызов vvod(float), стандартное преобразование

vvod((char)1); //вызов vvod(char), преобразование пользователя

Связывание оператора вызова функции с определением необходимой функции производится на этапе компиляции. Это, так называемое, раннее связывание, позволяющее получать наиболее оптимальный код.

В связанных классах (базовом и производном) наличие функций с одинаковой сигнатурой приводит к неоднозначности доступа. Программист должен сам заложить раннее связывание, т.е. для вызова необходимой функции использовать имя соответствующего класса с операцией разрешения видимости (::).

В C++ имеются средства, позволяющие переложить связывание вызова таких функций с определениями функций с программиста на саму программу во время ее выполнения. Это механизм виртуальных функций или позднее связывание.

Виртуальная функция - это элемент-функция базового класса в иерархии наследования со спецификатором virtual, переопределенная в производных классах и вызываемая в зависимости от класса объекта, адресуемого через указатель или ссылку на базовый класс.

Виртуальная функция имеет следующие свойства:

- определяется в базовом классе иерархии наследования со словом virtual и переопределяется в производных классах;

- имя, сигнатура и тип возвращаемого значения функций базового и производных классов должны совпадать;

- не может быть объявлена как static, так как наследуется;

- выбор соответствующего экземпляра функции производится через указатель (или ссылку) на базовый класс в зависимости от типа (класса) объекта, адресуемого указателем (или ссылкой), а не в зависимости от типа указателя (или ссылки);

- вызывается динамически во время выполнения программы, что приводит к увеличению времени выполнения.

Пусть имеется иерархия классов, показанная на рис. 3.5, в каждом из которых содержится элемент-функция void vyvod().

cl1 //работники предприятия

|

cl2 //работники цеха

|

cl3 //рабочий участка

Рис. 3.5. Иерархия классов

Требуется составить программу последовательного вызова функций vyvod() классов cl1, cl2, cl3.

Приведем следующие рассуждения. Так как каждый работник цеха и участка являются работниками предприятия, то указатели (или ссылки) на cl2 и cl3 можно присвоить указателям (или ссылкам) на cl1, т.е. объекты производных классов можно рассматривать, как объекты базового класса:

- объявление объектов: cl1 obj1; cl2 obj2; cl3 obj3;

- объявление указателей (или ссылок) на класс cl1: cl1* p[3]; или

cl1& r1; cl1& r2; cl1& r3;

- присваивание указателям (или ссылкам) на класс cl1 указатели (или ссылки) на классы cl2 и cl3: p[0]=&obj1; p[1]=&obj2; p[2]=&obj3; или r1=obj1; r2=obj2; r3=obj3;

Итак, вызываются последовательно экземпляры виртуальной функции vyvod() классов cl1, cl2, cl3, что и было необходимо.

Позднее (динамическое) связывание заключается в том, что связывание вызова функции с необходимым экземпляром функции осуществляется динамически во время выполнения программы. Вызов функции обрабатывается в пределах своего полиморфического кластера. Полиморфический кластер - это совокупность классов, в которых определяется и переопределяется виртуальная функция. Для каждого класса кластера создается таблица виртуальных функций (VT), содержащая адреса этих функций. Каждый объект класса получает скрытый указатель (VP) на соответствующую таблицу VT, который неявно инициализируется (с помощью конструктора) адресом таблицы VT и смещением в таблице VT адреса функции. Пример представлен в табл. 3.3.

Таблица 3.3.

Объекты классов Таблицы VT Функции классов
obj1: VP=(a1, hvyvod)---à //VT_cl1: -->a1)...(a1_vyvod)--à -->a1_vyvod)void vyvod() {...}
obj2: VP=(a2, hvyvod)---à //VT_cl2: -->a2)...(a2_vyvod)--à -->a2_vyvod)void vyvod() {...}
obj3: VP=(a3, hvyvod)---à //VT_cl3: -->a3)...(a3_vyvod)--à -->a3_vyvod)void vyvod() {...}

Рассмотрим алгоритм вызова функции vyvod() класса cl3:

- по адресу объекта в указателе (p[2]=&obj3;) находится объект (obj3) и определяется указатель VP на таблицу виртуальных функций VT (VP=(a3,hvyvod)), содержащий адрес таблицы (a3) и смещение в таблице VT адреса функции (hvyvod);

- по адресу таблицы (a3) и смещению (hvyvod) в таблице VT находится адрес виртуальной функции (a3_vyvod);

- по адресу функции (a3_vyvod) находится функция vyvod() класса cl3.

Абстрактные классы

В некоторых случаях виртуальная функция, объявленная в базовом классе не нужна для объектов этого класса, т.е. базовый класс не нуждается в своих объектах. Тогда базовый класс рассматривается как абстрактный класс.

Абстрактным классом называется базовый класс в иерархии наследования, содержащий чистые виртуальные функции, определенные в производных классах. Чистая виртуальная функция - это виртуальная функция, объявленная в абстрактном классе со спецификатором (=0) и определенная в производном классе. Пример:

class cl1 class cl2:public cl1 class cl3:public cl2

{ public: { public: { public:

virtual void vyvod()=0; void vyvod() {...}; void vyvod() {...};

}; } }

Абстрактный класс имеет следующие свойства:

- чистые виртуальные функции со спецификатором =0;

- если в производном классе чистая виртуальная функция не определена, то такой класс тоже считается абстрактным;

- не имеет объектов и используется как базовый класс;


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: