|
|
С помощью виртуальных функций можно преодолеть трудности, возникающие при использовании поля типа. В базовом классе описываются функции, которые могут переопределяться в любом производном классе. Транслятор и загрузчик обеспечат правильное соответствие между объектами и применяемыми к ним функциями:
class employee { char* name; short department; // ... employee* next; static employee* list; public: employee(char* n, int d); // ... static void print_list(); virtual void print() const; };
Служебное слово virtual (виртуальная) показывает, что функция print() может иметь разные версии в разных производных классах, а выбор нужной версии при вызове print() - это задача транслятора. Тип функции указывается в базовом классе и не может быть переопределен в производном классе. Определение виртуальной функции должно даваться для того класса, в котором она была впервые описана (если только она не является чисто виртуальной функцией, см. $$6.3). Например:
void employee::print() const { cout << name << '\t' << department << '\n'; // ... }
Мы видим, что виртуальную функцию можно использовать, даже если нет производных классов от ее класса. В производном же классе не обязательно переопределять виртуальную функцию, если она там не нужна. При построении производного класса надо определять только те функции, которые в нем действительно нужны:
class manager : public employee { employee* group; short level; // ... public: manager(char* n, int d); // ... void print() const; };
Место функции print_employee() заняли функции-члены print(), и она стала не нужна. Список служащих строит конструктор employee ($$6.2.2). Напечатать его можно так:
void employee::print_list() { for ( employee* p = list; p; p=p->next) p->print(); }
Данные о каждом служащем будут печататься в соответствии с типом записи о нем. Поэтому программа
int main()
{
employee e("J.Brown",1234);
manager m("J.Smith",2,1234);
employee::print_list();
}
напечатает
J.Smith 1234
level 2
J.Brown 1234
Обратите внимание, что функция печати будет работать даже в том случае,
если функция employee_list() была написана и оттранслирована еще до того,
как был задуман конкретный производный класс manager! Очевидно, что для
правильной работы виртуальной функции нужно в каждом объекте класса
employee хранить некоторую служебную информацию о типе. Как правило,
реализации в качестве такой информации используют просто указатель.
Этот указатель хранится только для объектов класса с виртуальными
функциями, но не для объектов всех классов, и даже для не для всех
объектов производных классов. Дополнительная память отводится только
для классов, в которых описаны виртуальные функции. Заметим, что
при использовании поля типа, для него все равно нужна дополнительная
память.
Если в вызове функции явно указана операция разрешения
области видимости ::, например, в вызове manager::print(),
то механизм вызова виртуальной функции не действует. Иначе подобный
вызов привел бы к бесконечной рекурсии. Уточнение имени функции
дает еще один положительный эффект: если виртуальная функция
является подстановкой (в этом нет ничего необычного), то в вызове
с операцией :: происходит подстановка тела функции. Это
эффективный способ вызова, который можно применять
в важных случаях, когда одна виртуальная функция
обращается к другой с одним и тем же объектом. Пример такого
случая - вызов функции manager::print(). Поскольку тип объекта
явно задается в самом вызове manager::print(), нет нужды определять
его в динамике для функции employee::print(), которая и будет
вызываться.
| |