Бьерн Страуструп - Язык программирования С++. Главы 5-8
Страница 33. Виртуальные функции



6.2.5 Виртуальные функции

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

         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(), которая и будет
 вызываться.

 
« Предыдущая статья   Следующая статья »