Чистые виртуальные функции
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 59 из 104.
Чистая виртуальная функция — это виртуальная функция, у которой нет реализации в базовом классе. Она объявляется присваиванием = 0 объявлению функции. Это сообщает компилятору, что производные классы обязаны предоставить собственную реализацию.
class Shape {
public:
virtual double area() = 0; // Чисто виртуальная функция
virtual ~Shape() = default;
};В отличие от обычных виртуальных функций, которые задают поведение по умолчанию, чистые виртуальные функции определяют контракт: любой конкретный производный class должен реализовать эту function, чтобы его можно было создать. Базовый class просто объявляет что должно быть сделано, а не как.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
};И Circle, и Rectangle должны реализовать area(), поскольку в Shape это чистая виртуальная функция. Если производному классу не удаётся реализовать все чистые виртуальные функции, он также становится абстрактным и не может быть создан.
Чистые виртуальные function необходимы, когда базовый class не может предоставить осмысленную реализацию по умолчанию. У каждой фигуры есть area, но невозможно разумно calculate «area generic shape», не зная конкретного типа фигуры.
Задание
ЛегкоДавайте создадим систему обработки платежей, которая демонстрирует, как чистые виртуальные функции обеспечивают соблюдение контракта для различных способов оплаты. Вы создадите абстрактный базовый класс, определяющий, что должен делать каждый обработчик платежей, а затем реализуете конкретные типы платежей, выполняющие этот контракт.
Вы организуете код в трёх файлах:
PaymentProcessor.h: определите абстрактный классPaymentProcessor, который служит основой для всех способов оплаты. Этот класс должен содержать:- защищённый член
std::string accountId - конструктор, инициализирующий идентификатор аккаунта
- чистый виртуальный метод
processPayment(double amount): каждый тип платежа должен реализовать его по-своему - чистый виртуальный метод
getProcessorName(), возвращающийstd::string - виртуальный деструктор
- защищённый член
PaymentMethods.h: реализуйте два конкретных обработчика платежей, наследующихPaymentProcessor:CreditCardProcessor:- закрытый член
double feePercentage(ставка комиссии за транзакцию) - конструктор, принимающий идентификатор аккаунта и процент комиссии
- реализуйте
processPayment(), чтобы вычислить комиссию (amount * feePercentage / 100), а затем вывести:Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>) - реализуйте
getProcessorName(), возвращающий"CreditCard"
BankTransferProcessor:- закрытый член
std::string bankName - конструктор, принимающий идентификатор аккаунта и название банка
- реализуйте
processPayment(), чтобы вывести:Bank Transfer [<accountId>] via <bankName>: Transferred $<amount> - реализуйте
getProcessorName(), возвращающий"BankTransfer"
- закрытый член
main.cpp: считайте четыре входных значения (каждое в отдельной строке):- идентификатор аккаунта кредитной карты
- процент комиссии кредитной карты (double)
- идентификатор банковского аккаунта
- название банка
Создайте оба обработчика платежей и сохраните их в массиве указателей
PaymentProcessor*. Выполните платёж на сумму100.0через каждый обработчик, отображая имя обработчика перед каждой транзакцией:Processing with <processorName>: <processPayment output>Выведите пустую строку между обработчиками. После завершения освободите память, выделенную динамически.
Например, для входных данных CC-4521, 2.5, BA-7890 и National Bank:
Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)
Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100Поскольку PaymentProcessor содержит чистые виртуальные функции, нельзя создать его экземпляр напрямую: можно создавать только конкретные реализации, выполняющие контракт. Это гарантирует, что каждый способ оплаты предоставляет собственную логику обработки.
Попробуйте сами
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Прочитать входные данные
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Создать массив указателей PaymentProcessor* с 2 элементами
// TODO: Создать объекты CreditCardProcessor и BankTransferProcessor
// и сохранить их в массиве
// TODO: Пройти по массиву и для каждого процессора:
// 1. Print "Processing with <processorName>:"
// 2. Вызвать processPayment с суммой 100.0
// 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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++