Виртуальной называется функция, объявленная в базовом классе с описателем virtual и переопределенная в одном или нескольких производных классах. Таким образом, каждый производный класс может иметь собственный вариант виртуальной функции. Свойства виртуальности проявляются у виртуальных функций, когда для их вызова используется указатель базового класса. Если виртуальная функция вызывается посредством указателя базового класса, С++ определяет, какой из вариантов этой виртуальной функции вызвать, основываясь на типе объекта, на который указывает указатель. Следовательно, если базовый класс содержит виртуальную функцию, и два или больше различных классов наследуют от этого базового класса, то в зависимости от того, на объект какого производного класса указывает указатель базового класса, будут вызываться различные варианты виртуальной функции.
Вы объявляете функцию виртуальной внутри базового класса, предваряя ее объявление ключевым словом virtual. Когда виртуальная функция переопределяется в производном классе, нет необходимости повторять ключевое слово virtual (хотя не будет ошибкой и его повторение).
Класс, который содержит виртуальные функции, называется полиморфным классом. Этот термин также относится и к классу, который наследует от базового класса, содержащего виртуальную функцию.
В следующей программе продемонстрирована работа с виртуальной функцией:
// Короткий пример использования виртуальной функции.
#include <iostream>
#include <conio>
using namespace std;
class B {
public:
virtual void who () { // Определим виртуальную функцию
Рассмотрим эту программу детально, чтобы понять, как она работает.
Как вы видите, функция who()объявлена в классе В как виртуальная. Это означает, что функцию можно переопределить в производном классе. Внутри каждого из производных классов D1 и D2 функция переопределяется относительно своего класса. Внутри main() объявлены четыре переменные: base_obj, представляющая собой объект типа В; р, являющаяся указателем на объекты класса В; наконец, Dl_obj и D2_obj — объекты двух производных классов. Далее переменной р присваивается адрес объекта base_obj, после чего вызывается функция who(). Поскольку who() объявлена как виртуальная, С++ определяет во время выполнения программы, какой вариант who() вызывать, основываясь на типе объекта, на который указывает р. В данном случае р указывает на объект типа В, поэтому вызывается вариант who() из класса В. Далее по ходу программы р присваивается адрес объекта Dl_obj. Вспомним, что указатель базового класса может ссылаться на объект любого производного класса. Теперь при вызове who() С++ снова анализирует, на объект какого типа указывает р и, в зависимости от этого типа, определяет, какой вариант who() следует вызвать. Поскольку р указывает на объект типа D1, выполняется вариант who() из этого класса. Аналогично, когда р присваивается адрес D2_obj, выполняется вариант who( ), объявленный внутри D2.
Подытожим: если виртуальная функция вызывается посредством указателя базового класса, выбор фактически выполняемого варианта виртуальной функции осуществляется во время выполнения, исходя из типа объекта, на который указывает этот указатель.
Хотя виртуальные функции обычно вызываются посредством указателей базового класса, их можно также вызывать обычным образом, используя синтаксис с оператором-точкой. Отсюда следует, что в предыдущем примере было бы синтаксически правильно вызвать who() с помощью такого предложения:
D1_obj .who ( ) ;
Однако вызов виртуальной функции таким образом игнорирует ее полиморфные атрибуты. Только когда виртуальная функция вызывается посредством указателя базового класса, достигается полиморфизм времени выполнения.
На первый взгляд описанное выше переопределение виртуальной функции в производном классе представляется особой формой перегрузки функции. Однако это не так. Фактически эти два средства фундаментально различаются. Прежде всего, перегруженная функция должна отличаться числом или типом своих параметров, в то время как переопределенная виртуальная функция должна иметь в точности то же число и те же типы параметров. Прототипы виртуальной функции и ее переопределений должны в точности совпадать. Если прототипы различаются, тогда функция рассматривается просто как перегруженная, и ее виртуальная сущность теряется. Другое ограничение заключается в том, что виртуальная функция обязана быть членом класса, для которого она определяется. Однако виртуальная функция может быть другом другого класса. Наконец, виртуальными могут быть деструкторы, но не конструкторы.