Добавить в корзинуПозвонить
Найти в Дзене

Абстрактные классы, виртуальные методы и полиморфизм

В этой статье разберем ключевые инструменты для реализации полиморфизма (одного из трёх столпов ООП наряду с инкапсуляцией и наследованием) на примере языка С++ — абстрактные классы и виртуальные методы. Виртуальный метод — метод класса, который может быть переопределён в классе‑наследнике. При вызове через указатель или ссылку на базовый класс выполняется версия метода из производного класса. Как работает: Абстрактный класс — класс, который: Ключевые особенности: Создадим класс Person c полями спецификатора protected — name и age. Спецификатор — ключевое слово в C++, которое изменяет свойства или поведение элементов языка (типов, функций, методов, переменных). В нашем коде это спецификаторы доступа, которые управляют видимостью членов класса: Мы не хотим, чтобы пользователь напрямую мог изменить name и age для объекта, но нам нужно, чтобы у класса-наследника был доступ к этим полям, поэтому используем спецификатор protected. Класс Person является абстрактным, поскольку в нем есть два
Оглавление

В этой статье разберем ключевые инструменты для реализации полиморфизма (одного из трёх столпов ООП наряду с инкапсуляцией и наследованием) на примере языка С++ — абстрактные классы и виртуальные методы.

Основные понятия

Виртуальный метод — метод класса, который может быть переопределён в классе‑наследнике. При вызове через указатель или ссылку на базовый класс выполняется версия метода из производного класса.

Как работает:

  • объявляется с ключевым словом virtual в базовом классе;
  • позволяет реализовать позднее связывание (вызов нужной версии метода определяется во время выполнения);
  • даёт возможность писать код, работающий с объектами разных типов через единый интерфейс.

Абстрактный класс — класс, который:

  • содержит хотя бы один чистый виртуальный метод (= 0);
  • не может быть инстанцирован (нельзя создать объект такого класса);
  • задаёт контракт — наследники обязаны реализовать все чистые виртуальные методы.

Ключевые особенности:

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

Работаем с классами в С++

Создадим класс Person c полями спецификатора protectedname и age. Спецификатор — ключевое слово в C++, которое изменяет свойства или поведение элементов языка (типов, функций, методов, переменных). В нашем коде это спецификаторы доступа, которые управляют видимостью членов класса:

  • public — доступен отовсюду;
  • private — доступен только внутри класса;
  • protected — доступен в классе и его наследниках.

Мы не хотим, чтобы пользователь напрямую мог изменить name и age для объекта, но нам нужно, чтобы у класса-наследника был доступ к этим полям, поэтому используем спецификатор protected.

Абстрактный класс Person на С++
Абстрактный класс Person на С++

Класс Person является абстрактным, поскольку в нем есть два чистых виртуальных метода — input() и output().

Разбор синтаксиса:

  • virtual — метод может быть переопределён в производных классах;
  • void input() — сигнатура метода (возвращает void, принимает ничего);
  • = 0 — делает метод «чистым», то есть без реализации в этом классе.

Последствия:

  • класс с хотя бы одним чисто виртуальным методом становится абстрактным (если виртуальный метод будет не чистым, то класс перестанет быть абстрактым. Обсудим чуть позже);
  • нельзя создать объект такого класса: Person p; — ошибка компиляции;

Все производные классы обязаны переопределить этот метод.

Поскольку класс объекта Person создать нельзя, то можно сказать, что это «шаблон» с правилами. Сам по себе не используется, но задаёт, что должны уметь его «дети».

Виртуальные методы выполняют следующие функции:

  • Задают контракт (интерфейс). Любой класс, унаследованный от Person, обязан реализовать эти методы.
  • Делают класс абстрактным — нельзя создать объект Person.

Кроме виртуальных методов в классе Person есть виртуальный деструктор с реализацией по умолчанию. Что делает:

  • virtual — указывает, что деструктор будет вызываться полиморфно (через указатель на базовый класс);
  • = default — компилятор сгенерирует стандартную реализацию деструктора (аналог пустого тела {}).

При удалении объекта:

  1. Сначала вызывается деструктор производного класса (~Student()).
  2. Затем вызывается деструктор базового класса (~Person()).

Без виртуального деструктора:

  • вызывается только ~Person();
  • ~Student() не вызывается;
  • ресурсы производного класса (например, динамические массивы) не освобождаются — утечка памяти.

Селекторы и модификаторы выполняют классический функционал обычных классов.

Создадим производный класс Student:

Класс-наследник Student
Класс-наследник Student

Перед классом родителем снова указываем спецификатор — public. Таким образом, все методы public, объявленные в Person, сохранятся открытыми и в Student. Поля protected так же останутся недоступными извне, но доступными для наследования.

В классе Student создаем новое поле — rating, оно будет закрытым.

Реализация виртуального метода input() в Student
Реализация виртуального метода input() в Student

В Student получают свою функциональность виртуальные методы.

overrideспецификатор в C++, который явно указывает: данный метод переопределяет виртуальный метод из базового класса. Компилятор проверяет, действительно ли такой метод существует в иерархии наследования. Если в базовом классе нет подходящего виртуального метода, компилятор выдаст ошибку.

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

Напишем также реализацию для функции output():

Реализация виртуального метода output() в Student
Реализация виртуального метода output() в Student

Класс Student является полноценным классом, на основе которого можно создавать объекты, поэтому input() и output() мы тоже можем использовать для решения задач.

Если бы мы не написали код для виртуальных функций в классе Student, то этот класс так же считался бы абстрактным и мы бы не смогли создать на его основе объекты.

О связи с полиморфизмом

Полиморфизм — способность объектов разных классов по‑разному реагировать на один и тот же вызов.

Для полиморфизма нужны:

  1. Наследование (иерархия классов).
  2. Виртуальные методы (механизм динамического связывания).
  3. Указатели/ссылки на базовый класс.

Почему обычные методы не подходят?

При вызове обычного метода компилятор «запоминает» тип указателя на этапе компиляции и всегда вызывает одну и ту же версию метода. А это уже статическое связывание.

Это именно как раз тот случай, когда нам может пригодится виртуальный метод с телом функции, а не чистый.

Разберем на примере. Давайте создадим систему с разными типами людей:

Пример полиморфизма в С++
Пример полиморфизма в С++

В классе Person виртуальный метод не является чистым, а значит класс не будет являться абстрактным.

Теперь напишем функцию, которая работает с любым типом Person:

Пример полиморфизма в С++
Пример полиморфизма в С++

Что здесь происходит:

  1. У нас есть одна функция greet().
  2. Она принимает указатель на Person.
  3. Но может работать с любым объектом, унаследованным от Person.
  4. При вызове p->introduce() программа «смотрит», на какой реальный объект указывает p, и вызывает правильный метод.

Если написать этот же пример без виртуальных методов, то результат будет один для всех объектов — результат работы функции introduce() класса Person.

Абстрактные классы, виртуальные методы и полиморфизм делают код:

  • гибким — легко менять поведение через переопределение методов;
  • масштабируемым — просто добавлять новые классы‑наследники;
  • чистым — меньше дублирования, чёткое разделение обязанностей;
  • надёжным — компилятор ловит ошибки на этапе сборки (например, если не реализован обязательный метод).

Буду рад обратной связи и ответить на все ваши вопросы в комментариях.