Абстрактные классы
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 60 из 104.
Абстрактный класс — это любой класс, который содержит как минимум одну чисто виртуальную функцию. Поскольку у него неполный интерфейс, вы не можете создавать объекты абстрактного класса напрямую — он существует исключительно для того, чтобы от него наследовались.
class Shape {
public:
virtual double area() = 0; // Чисто виртуальная функция — делает Shape абстрактным
virtual double perimeter() = 0; // Еще одна чисто виртуальная функция
void printInfo() { // Обычный метод — имеет реализацию
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // Ошибка: невозможно создать экземпляр абстрактного классаАбстрактные классы могут содержать смесь чистых виртуальных функций (без реализации), обычных виртуальных функций (с реализацией по умолчанию) и невиртуальных функций. Это позволяет определять общее поведение, требуя при этом от производных классов реализации специфической функциональности.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: Circle реализует все чисто виртуальные функции
c.printInfo(); // Использует унаследованный printInfo(), вызывает area() класса CircleПроизводный класс должен реализовать все чисто виртуальные функции, чтобы стать конкретным (допускающим создание экземпляров). Если он оставит какую-либо из них нереализованной, он сам останется абстрактным. Этот механизм гарантирует, что каждый конкретный класс в вашей иерархии предоставляет необходимую функциональность.
Задание
ЛегкоДавайте создадим систему медиаплеера, которая демонстрирует, как абстрактные классы определяют контракты для различных типов медиа. Вы создадите абстрактный базовый класс, который устанавливает, что должен делать каждый медиаплеер, а затем реализуете конкретные плееры для аудио- и видеоконтента.
Вы организуете свой код в трех файлах:
MediaPlayer.h: Определите абстрактный классMediaPlayer, который служит чертежом для всех типов медиа. Ваш абстрактный класс должен иметь:- Защищенный член
std::string title - Защищенный член
int duration(в секундах) - Конструктор, который инициализирует оба значения
- Чисто виртуальный метод
play()— каждый тип медиа будет воспроизводиться по-разному - Чисто виртуальный метод
getMediaType(), возвращающийstd::string - Обычный (невиртуальный) метод
showInfo(), который выводит:<title> (<duration>s) - Type: <mediaType>— этот метод должен вызыватьgetMediaType()для получения типа - Виртуальный деструктор
- Защищенный член
Players.h: Реализуйте два конкретных медиаплеера, наследуемых отMediaPlayer:AudioPlayer:- Приватный член
std::string artist - Конструктор, принимающий название, длительность и исполнителя
- Реализуйте
play()для вывода:Playing audio: <title> by <artist> - Реализуйте
getMediaType(), чтобы он возвращал"Audio"
VideoPlayer:- Приватный член
std::string resolution - Конструктор, принимающий название, длительность и разрешение
- Реализуйте
play()для вывода:Playing video: <title> in <resolution> - Реализуйте
getMediaType(), чтобы он возвращал"Video"
- Приватный член
main.cpp: Считайте четыре входных значения (каждое на отдельной строке):- Название аудиодорожки
- Длительность аудио (целое число)
- Название видео
- Длительность видео (целое число)
Создайте
AudioPlayerс исполнителем"Unknown Artist"иVideoPlayerс разрешением"1080p". Сохраните оба объекта в массиве указателейMediaPlayer*.Пройдите циклом по массиву и для каждого плеера сначала вызовите
showInfo(), а затемplay(). Выведите пустую строку между элементами медиа. Очистите динамически выделенные объекты по завершении.
Например, при входных данных Summer Vibes, 180, Nature Documentary и 3600:
Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist
Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080pОбратите внимание, как метод showInfo() определен один раз в абстрактном базовом классе, но вызывает чисто виртуальный метод getMediaType() — это демонстрирует, как абстрактные классы могут смешивать конкретные реализации с методами, которые должны предоставить производные классы. Не забудьте использовать ключевое слово override для всех переопределенных методов.
Шпаргалка
Абстрактный класс содержит как минимум одну чистую виртуальную функцию и не может быть инстанцирован напрямую. Он существует для того, чтобы наследоваться и определять интерфейс для производных классов.
Определение абстрактного класса
Используйте = 0 для объявления чистой виртуальной функции:
class Shape {
public:
virtual double area() = 0; // Чистая виртуальная функция
virtual double perimeter() = 0; // Делает Shape абстрактным
void printInfo() { // Обычный метод с реализацией
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default; // Виртуальный деструктор
};Абстрактные классы могут содержать:
- Чистые виртуальные функции (без реализации)
- Обычные виртуальные функции (с реализацией по умолчанию)
- Невиртуальные функции
Реализация конкретных классов
Производный класс должен реализовать все чистые виртуальные функции, чтобы стать инстанцируемым:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: все чистые виртуальные функции реализованы
c.printInfo(); // Унаследованный метод вызывает area() класса CircleЕсли производный класс не реализует все чистые виртуальные функции, он остается абстрактным и не может быть инстанцирован.
Попробуйте сами
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// Чтение входных данных
std::string audioTitle;
int audioDuration;
std::string videoTitle;
int videoDuration;
std::getline(std::cin, audioTitle);
std::cin >> audioDuration;
std::cin.ignore();
std::getline(std::cin, videoTitle);
std::cin >> videoDuration;
// TODO: Создайте AudioPlayer с исполнителем "Unknown Artist"
// TODO: Создайте VideoPlayer с разрешением "1080p"
// TODO: Сохраните оба объекта в массиве указателей MediaPlayer*
// TODO: Пройдите циклом по массиву и для каждого плеера:
// 1. Вызовите showInfo()
// 2. Вызовите play()
// 3. Выведите пустую строку между каждым медиа-элементом
// TODO: Очистите динамически выделенные объекты
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер