Skip to content

Intermediate · Operator overloading, inheritance and polymorphism · topic 20 of 42 · 25 min

Abstract classes, interfaces and good class design

Explanation

A pure virtual function (= 0) has no body in the base class, which makes the class abstract: you cannot create objects of it, only of derived classes that implement every pure virtual function. Abstract classes with only pure virtual functions act as interfaces. Combine them with composition for clean, testable designs.

Pure virtual functions

Sometimes a base class cannot sensibly do the job itself. What is the area of a "Shape" in general? There is no answer. Writing = 0 after a virtual function declares it pure virtual: the base gives no implementation, and every concrete derived class must provide one.

cpp
class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;     // pure virtual
};

// Shape s;            // error: cannot create an object of abstract class Shape
class Square : public Shape {
public:
    explicit Square(double side) : side_(side) {}
    double area() const override { return side_ * side_; }
private:
    double side_;
};
Square sq(3);          // fine: Square implements area()
A class with at least one pure virtual function is abstract. A derived class that does not override all of them is still abstract.

Interfaces

Java and C# have an interface keyword. C++ does not; instead, an interface is an abstract class that has only pure virtual functions and a virtual destructor, and no data. Code that uses the interface depends only on "what can be done", not "how it is done". This makes it easy to swap implementations, for example a real SMS gateway in production and a fake one in tests.

cpp
class PaymentGateway {                  // an interface
public:
    virtual ~PaymentGateway() = default;
    virtual bool charge(const std::string& account, long long paise) = 0;
};

class Checkout {
public:
    explicit Checkout(PaymentGateway& gw) : gw_(gw) {}   // depends on the interface
    bool placeOrder(const std::string& acc, long long paise) {
        return paise > 0 && gw_.charge(acc, paise);
    }
private:
    PaymentGateway& gw_;
};

class FakeGateway : public PaymentGateway {             // used in tests
public:
    bool charge(const std::string&, long long) override { ++calls; return true; }
    int calls = 0;
};
Checkout never knows which gateway it talks to. A test passes a FakeGateway and checks calls.
Think of it like this: An interface is like the standard electric socket in your house. The socket only promises "230 volts here". A fan, a phone charger or a mixer can plug in. The wall does not care which device it is, and you can replace the fan without rewiring the house.

Composition over inheritance, again

Deep inheritance trees (Vehicle → Car → ElectricCar → LuxuryElectricCar) become rigid: a change at the top ripples down everywhere. Many modern designs keep inheritance shallow: an interface at the top and concrete classes directly below it, with extra features added by holding other objects as members (composition).

Multiple inheritance, briefly

C++ allows a class to inherit from several bases: class Phone : public Camera, public MusicPlayer. Inheriting from several INTERFACES is common and safe. Inheriting data from several bases that share a common base leads to the "diamond problem" (two copies of the shared base), which C++ solves with virtual inheritance. As a learner, avoid diamonds; prefer interfaces plus composition.

Design checklist

  1. Does the derived class truly satisfy "is-a" everywhere the base is used? If not, use composition.
  2. Is the base class meant to be used polymorphically? Then give it a virtual destructor.
  3. Mark every override with override.
  4. Keep data private; give derived classes protected functions rather than protected data when you can.
  5. Hold polymorphic objects in std::unique_ptr<Base> (single owner) inside containers.
Careful: You cannot store abstract types by value: std::vector<Shape> does not compile, and std::vector<Square> loses polymorphism. Use std::vector<std::unique_ptr<Shape>>.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Abstract classes, interfaces and good class design · 1/6

Abstract classes

Some base classes cannot do the job themselves. They only describe what must be done.

Prefer a video by a teacher? Find free videos on “c++ abstract class pure virtual function interface design” on YouTube ↗

In real life: Swappable storage in a photo-backup app

A backup app can save photos to local disk, to a company server or to a cloud bucket. The upload logic depends on a Storage interface with save() and exists(). In tests, an InMemoryStorage implementation runs in milliseconds without touching the network; in production, the cloud implementation is plugged in.

cpp
#include <iostream>
#include <map>
#include <string>

class Storage {
public:
    virtual ~Storage() = default;
    virtual void save(const std::string& key, const std::string& data) = 0;
    virtual bool exists(const std::string& key) const = 0;
};

class InMemoryStorage : public Storage {
public:
    void save(const std::string& key, const std::string& data) override { files_[key] = data; }
    bool exists(const std::string& key) const override { return files_.count(key) > 0; }
private:
    std::map<std::string, std::string> files_;
};

void backup(Storage& store, const std::string& name, const std::string& bytes) {
    if (!store.exists(name)) store.save(name, bytes);
}

int main() {
    InMemoryStorage mem;
    backup(mem, "IMG_001.jpg", "....");
    std::cout << mem.exists("IMG_001.jpg") << '\n';   // 1
}
backup() works with any Storage, real or fake.

Practice (0/4 done)

Task 1 · Question

What makes a class abstract?

Task 2 · Question

class A { public: virtual void f() = 0; virtual void g() = 0; }; class B : public A { public: void f() override {} }; Can you create B b;?

Task 3 · Question

Which container correctly holds different shapes polymorphically?

Task 4 · Practice

Write an interface Logger with a pure virtual void log(const std::string&) and a virtual destructor. Implement ConsoleLogger (prints) and MemoryLogger (stores lines in a std::vector<std::string>). Write a class OrderService that takes a Logger& in its constructor and logs "order <id> placed". In main, use MemoryLogger and print how many lines were logged.

Quick quiz

Question 1

1.How do you write an interface in C++?

Question 2

2.Why do interfaces make testing easier?

Question 3

3.What is the diamond problem?

Question 4

4.Can an abstract class have a constructor and data members?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

How do you write an interface in C++?

Answer: As an abstract class with only pure virtual functions and a virtual destructor

C++ has no interface keyword; abstract classes play that role.

Why do interfaces make testing easier?

Answer: You can pass a fake implementation instead of a real network or database one

Code that depends on an interface can be tested with a fake or in-memory implementation.

What is the diamond problem?

Answer: A class getting two copies of a shared base through two inheritance paths

Virtual inheritance solves it; better still, avoid diamonds of data-carrying classes.

Can an abstract class have a constructor and data members?

Answer: Yes; it just cannot be instantiated on its own

Abstract only means "cannot be created directly". It may still hold shared data and constructors for derived classes.

Can an interface function have a default body in Kotlin?Related

Answer: Yes

Interfaces can provide default implementations.

From Inheritance and interfaces

How many base classes can a C# class have?Related

Answer: One

C# has single class inheritance; use interfaces for more abilities.

From Inheritance: base and derived classes

What must a non-abstract class do with an inherited abstract method?Related

Answer: Override it

Otherwise error CS0534.

From Abstract classes and polymorphism

Which base members can a derived class use directly?Related

Answer: public and protected

private members stay hidden even from derived classes.

From Inheritance: building on an existing class

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Operator overloading, inheritance and polymorphism”. The module exam comes next.
  2. Module 5 exam: Operators, inheritance and polymorphism · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, The Standard Template Library (STL). 42 topics left in the course before the final exam.

← Previous: Virtual functions and polymorphism