Виртуальные методы

Если имя метода в производном классе совпадает с именем метода из базового класса, то будет использоваться метод из производного класса. В этом случае тип и количество параметров у методов должны совпадать, в противном случае производится перегрузка метода, а не переопределение. Чтобы вызвать метод базового класса из метода производного класса, следует указать перед методом название базового класса и оператор ::. Рассмотрим переопределение методов на примере (листинг 13.29).

Листинг 13.29. Переопределение методов

#include <iostream>

class A {
public:
   void func();
};
class B : public A {
public:
   void func();
};
void A::func() { std::cout << "A::func()" << std::endl; }
void B::func() {
   std::cout << "B::func()" << std::endl;
   // A::func(); // Вызов метода из базового класса
}

int main() {
   B obj;
   obj.func();               // B::func()
   return 0;
}

В этом примере в классах A и B объявлен метод func(). Класс B наследует класс A. Следовательно, метод из класса B переопределяет одноименный метод из класса A. Поэтому при создании объекта класса B будет вызван метод именно из производного класса. Таким образом достигается статический полиморфизм.

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

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

Листинг 13.30. Виртуальные методы

#include <iostream>

class A {
public:
   virtual void func(); // Виртуальный метод
   virtual ~A() {}      // Деструктор также должен быть виртуальным
};
class B : public A {
public:
   void func() override;
};
void A::func() { std::cout << "A::func()" << std::endl; }
void B::func() { std::cout << "B::func()" << std::endl; }

void test(A &obj) { obj.func(); }

int main() {
   A *pA, a;
   B b;
   pA = &a;                  // Адрес базового класса
   pA->func();               // A::func()
   pA = &b;                  // Адрес производного класса
   pA->func();               // B::func()
   test(a);                  // A::func()
   test(b);                  // B::func()
   return 0;
}

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

В процессе переопределения метода мы можем что-нибудь напутать и создать одноименный метод, но с другой сигнатурой. В результате мы получим перегрузку метода, а не переопределение. Компилятор ведь не знает, что мы хотим сделать, а перегрузка — это допустимая операция, и никаких ошибок здесь нет. Чтобы сообщить компилятору о переопределении, после списка параметров следует указать спецификатор override. Увидев спецификатор override, компилятор проверит наличие метода с указанной сигнатурой в базовых классах. Если такого метода нет, то выведет сообщение об ошибке. В результате мы застрахуем себя от ошибочных действий. Пример указания спецификатора:

void func() override;
На заметку

Учебник C++ (MinGW-W64)
Учебник C++ (MinGW-W64) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов