Skip to content
Laviyx Learn

Try

C++ course menu

C++ Abstract Classes and Interfaces: Pure Virtual Functions

Learn abstract classes in C++: pure virtual functions with = 0, why abstract classes cannot be instantiated, designing interfaces, mixing interfaces with multiple inheritance, the template method pattern, and dependency injection for testable code.

Intermediate13 min readUpdated
On this page

An abstract class describes what a family of classes can do, without saying how. It declares one or more pure virtual functions, which derived classes must implement. You cannot create an object of an abstract class itself; you can only create objects of the concrete classes derived from it.

Pure virtual functions#

Write = 0 instead of a function body:

C++
#include <iostream>
#include <string>

class Payment {
public:
    virtual bool pay(double amount) = 0;            // pure virtual
    virtual std::string method() const = 0;         // pure virtual
    virtual ~Payment() = default;

    void receipt(double amount) const {             // ordinary member function
        std::cout << "Paid " << amount << " by " << method() << '\n';
    }
};

class CardPayment : public Payment {
public:
    bool pay(double amount) override { return amount <= limit_; }
    std::string method() const override { return "card"; }

private:
    double limit_ = 50000;
};

class UpiPayment : public Payment {
public:
    bool pay(double amount) override { return amount <= 100000; }
    std::string method() const override { return "UPI"; }
};

int main() {
    CardPayment card;
    UpiPayment upi;

    for (Payment* p : {static_cast<Payment*>(&card), static_cast<Payment*>(&upi)}) {
        if (p->pay(2500)) p->receipt(2500);
    }
}
Output
Paid 2500 by card
Paid 2500 by UPI

Payment is abstract. Each derived class that implements every pure virtual function becomes concrete, and can be created.

Abstract classes cannot be instantiated#

C++
class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

int main() {
    Shape s;   // error: cannot declare variable 's' to be of abstract type 'Shape'
}

The same error appears if a derived class forgets to implement one of the pure virtual functions: it stays abstract too. The compiler's message lists the function that is missing.

Interfaces#

C++ has no interface keyword (unlike Java), but a class with only pure virtual functions and a virtual destructor plays exactly that role. Interfaces are small and focused, and a class can implement several of them:

C++
#include <iostream>
#include <string>

class Printable {
public:
    virtual std::string to_text() const = 0;
    virtual ~Printable() = default;
};

class Saveable {
public:
    virtual bool save() const = 0;
    virtual ~Saveable() = default;
};

class Invoice : public Printable, public Saveable {
public:
    explicit Invoice(int number) : number_(number) {}
    std::string to_text() const override { return "Invoice #" + std::to_string(number_); }
    bool save() const override {
        std::cout << "saving " << to_text() << '\n';
        return true;
    }

private:
    int number_;
};

void print(const Printable& item) {
    std::cout << item.to_text() << '\n';
}

int main() {
    Invoice inv(1042);
    print(inv);
    inv.save();
}
Output
Invoice #1042
saving Invoice #1042

print accepts anything printable, and knows nothing about invoices. This is the main benefit of interfaces: code depends on a small promise, not on a specific class.

A pure virtual function with shared steps#

An abstract base can implement the overall algorithm and leave individual steps to subclasses. This is called the template method pattern:

C++
#include <iostream>
#include <string>

class Report {
public:
    void generate() const {           // fixed overall steps
        std::cout << "== " << title() << " ==\n";
        body();
        std::cout << "-- end --\n";
    }
    virtual ~Report() = default;

protected:
    virtual std::string title() const = 0;
    virtual void body() const = 0;
};

class SalesReport : public Report {
protected:
    std::string title() const override { return "Sales"; }
    void body() const override { std::cout << "Revenue: 1,20,000\n"; }
};

int main() {
    SalesReport r;
    r.generate();
}
Output
== Sales ==
Revenue: 1,20,000
-- end --

Interfaces make code testable#

When a class depends on an interface rather than a concrete class, you can swap in a different implementation, such as a fake one for tests. This is called dependency injection:

C++
#include <iostream>
#include <string>

class Clock {
public:
    virtual int hour() const = 0;
    virtual ~Clock() = default;
};

class FixedClock : public Clock {      // a test double: always returns the same hour
public:
    explicit FixedClock(int h) : h_(h) {}
    int hour() const override { return h_; }

private:
    int h_;
};

std::string greeting(const Clock& clock) {
    int h = clock.hour();
    if (h < 12) return "Good morning";
    if (h < 17) return "Good afternoon";
    return "Good evening";
}

int main() {
    std::cout << greeting(FixedClock(9)) << '\n';
    std::cout << greeting(FixedClock(20)) << '\n';
}
Output
Good morning
Good evening

In the real program you would pass a SystemClock that reads the actual time; in tests, FixedClock makes the result predictable.

Abstract classes vs templates#

Virtual functions choose behaviour at run time and let you mix different types in one container. Templates (Templates) choose at compile time, with no virtual-call overhead, but every type must be known while compiling. Both are ways to write code that works with many types; abstract classes are the right tool when the concrete type is only known while the program runs.

Common mistakes#

Practice#

Practice exercise

Define an interface Discount with double apply(double price) const = 0. Implement PercentOff (e.g. 10% off) and FlatOff (e.g. 150 off, but never below 0). Apply both to a price of 1200 and to 100, and print the results.

Show solution
C++
#include <algorithm>
#include <iostream>

class Discount {
public:
    virtual double apply(double price) const = 0;
    virtual ~Discount() = default;
};

class PercentOff : public Discount {
public:
    explicit PercentOff(double percent) : percent_(percent) {}
    double apply(double price) const override { return price * (1 - percent_ / 100); }

private:
    double percent_;
};

class FlatOff : public Discount {
public:
    explicit FlatOff(double amount) : amount_(amount) {}
    double apply(double price) const override { return std::max(0.0, price - amount_); }

private:
    double amount_;
};

int main() {
    PercentOff ten(10);
    FlatOff flat(150);
    for (double price : {1200.0, 100.0}) {
        std::cout << price << ": " << ten.apply(price) << ", " << flat.apply(price) << '\n';
    }
}
Output
1200: 1080, 1050
100: 90, 0

Key takeaway#

Finished this lesson?