Шаг 31.
Виртуальные функции (окончание)

    На этом шаге мы подведем итоги по использованию виртуальных функций.

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

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

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

    Сказанное иллюстрирует следующая программа.

//OOР31_1.СРР - некоторые особенности виртуальных функций.
#include <iostream.h>
#include <conio.h>
struct base  {
   virtual void f1(void) { cout << "\nbase::f1"; } 
   virtual void f2(void) { cout << "\nbase::f2"; }
   virtual void f3(void) { cout << "\nbase::f3"; }
}; 
struct dir: public base {
   // Виртуальная функция:
   void f1(void) { cout << "\ndir::f1"; }
   // Ошибка в типе функции:
   // int f2(void) { cout << "\ndir::f2"; }
   // Невиртуальная функция:
   void f3(int i) { cout << "\ndir::f3::i = " << i; } 
};
void main(void) 
{ 
  base B, *pb = &B; 
  dir D, *pd = &D;
  pb->f1();
  pb->f2(); 
  pb->f3();
  pd->f1();
  pd->f2();
  // Ошибка при попытке без параметра вызвать dir::f3(int).
  // pd->f3();
  pd->f3(0) ;
  pb = &D;
  pb->f2(); 
  pb->f3();
  // Ошибочное употребление или параметра, или указателя: 
  // pb->f3(3);
}
Текст этой программы можно взять здесь.

    Обратите внимание, что три виртуальные функции базового класса по-разному воспринимаются в производном классе. dir::f1() - виртуальная функция, подменяющая функцию base::f1(). Функция base::f2() наследуется в классе dir так же, как и функция base::f3(). Функция dir::f3(int) - совершенно новая компонентная функция производного класса, никак не связанная с базовым классом. Именно поэтому невозможен вызов f3(int) через указатель на базовый класс. Виртуальные функции base::f2() и base::f3() оказались не переопределенными в производном классе dir. Поэтому при всех вызовах без параметров f3() используется только компонентная функция базового класса. Функция dir::f3(int) иллюстрирует соглашение языка о том, что если у функции производного класса набор параметров отличается от набора параметров соответствующей виртуальной функции базового класса, то это не виртуальная функция, а новый метод производного класса.

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

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

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

struct base {
      virtual int f(int j) { return j * j; } 
};
struct dir: public base { 
     int f(int i) { return base::f(i * 2); }
};

    На следующем шаге мы рассмотрим абстрактные классы.




Предыдущий шаг Содержание Следующий шаг