Lesson 4 · 4 of 20
Abstract classes and polymorphism · C++
Define an interface with pure virtual functions in C++ and handle different objects through base pointers: payment methods and their fees.

PaymentMethod is an abstract class: name() and fee() are pure virtual (they end in = 0) and have no body. You cannot create an object of it (PaymentMethod p; does not compile); it acts as an interface, a contract the derived classes fulfil. It uses struct, which is the same as class except everything is public by default.
Even so, the abstract class can hold shared code: total() adds the amount and the fee by calling fee(), without knowing which payment method it is. Card, PayPal and Transfer only say their name and how they work out the fee. Transfer charges a flat fee and ignores the amount, so its parameter has no name.
Polymorphism shows up with pointers to the base: methods is a std::vector<const PaymentMethod*> holding the addresses of three objects of different types (&card, &paypal, &transfer). With m->name() and m->total(amount) (the arrow -> calls a method through a pointer), each object runs its own version.
The loop finds the cheapest option for each amount by keeping a pointer to the best one: for 20 euros the card wins (a 0.30 fee) and for 80 the transfer does (a flat 1 euro against 1.20). Adding another payment method would only take another derived class: the loop stays the same.
Here the objects live in main and the pointers just refer to them. When objects are created with new and deleted through the base, the virtual destructor is essential; lesson 7 shows how smart pointers take care of freeing them.
Code · main.cpp
#include <iostream>#include <vector>struct PaymentMethod { // abstract: pure virtual functions (= 0), no objects of it virtual ~PaymentMethod() = default; virtual const char* name() const = 0; virtual double fee(double amount) const = 0; double total(double amount) const { return amount + fee(amount); }};struct Card : PaymentMethod { const char* name() const override { return "Card"; } double fee(double amount) const override { return amount * 0.015; }};struct PayPal : PaymentMethod { const char* name() const override { return "PayPal"; } double fee(double amount) const override { return 0.35 + amount * 0.029; }};struct Transfer : PaymentMethod { const char* name() const override { return "Transfer"; } double fee(double) const override { return 1.0; } // flat fee};int main() { Card card; PayPal paypal; Transfer transfer; std::vector<const PaymentMethod*> methods = {&card, &paypal, &transfer}; // pointers to the base for (double amount : {20.0, 80.0}) { const PaymentMethod* best = methods[0]; for (const PaymentMethod* m : methods) { std::cout << amount << " EUR by " << m->name() << ": " << m->total(amount) << "\n"; if (m->fee(amount) < best->fee(amount)) best = m; } std::cout << " cheapest: " << best->name() << "\n"; }}Output · c++ -std=c++17 main.cpp -o main && ./main
20 EUR by Card: 20.3 20 EUR by PayPal: 20.93 20 EUR by Transfer: 21 cheapest: Card 80 EUR by Card: 81.2 80 EUR by PayPal: 82.67 80 EUR by Transfer: 81 cheapest: Transfer
Key points
- A pure virtual function (
= 0) has no body and derived classes must provide one. - A class with any pure virtual function is abstract: no objects of it can exist.
- A pointer to the base can point to any derived object.
p->method()runs the real object's version.- The base of a polymorphic hierarchy needs a virtual destructor.
Exercise
Add a fee-free Cash payment method named "Cash", put it in methods and check that it becomes the cheapest for both amounts.