Теоретические сведения для студентов специальностей «Экономика и организация производства», страница 23

Тема 8. ВИРТУАЛЬНЫЕ ФУНКЦИИ

Программирование решения задачи в ООП следует начинать с разработки иерархической структуры классов и объектов, которые участвуют в реализуемом процессе.

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

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

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

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

Таким образом, данная совокупность методов удовлетворяет определению виртуальных. Пусть иерархия классов имеет в некотором базовом классе Base (самом верхнем в иерархии, где метод впервые встречается) прототип "virtual void my_virt()" и некоторый реализующий код. В каждом из производных классов, где нужна прорисовка, метод переопределяется с той же сигнатурой (можно уже без ключа virtual) и собственным реализующим кодом.

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

В программах на С++ это достаточно элегантно реализуется с помощью, например, механизма указателей. Так, если объявить указатель на базовый класс Base (Base * ptr), в котором введено определение виртуального метода для прорисовки объекта - my_virt, то вызов pД>my_virt будет выбирать различные версии виртуальной функции в зависимости от того, на объект какого класса мы установим указатель. Пусть в программе объявлены объект базового класса my_obj_1 - (Base my_obj_1) и объект производного класса my_obj_2 - (Derive my_obj_2). В каждом из классов прорисовка объекта выполняется своим собственным виртуальным методом с именем my_virt(). Тогда при инициализации указателя адресом объекта базового класса (ptr=&my_obj_1) запись ptr->my_virt приведет к вызову метода из класса Base, в то время как при инициализации (ptr=&my_obj_2) такая же запись ptr->my_virt вызовет метод из производного класса Derive.

При использовании виртуальных функций имеют место следующие ограничения: