Skip to content
C++ Better Explained
Go back
C++ Abstract Classes Explained: Pure Virtual Functions and Interfaces for Beginners
Edit page

C++ Abstract Classes: Pure Virtual Functions and Interfaces

An abstract class is a class you’re not allowed to create objects from directly. It exists purely to serve as a blueprint that other classes must follow.

The idea: when you have a concept like “Shape” that’s too general to be meaningful on its own, you make it abstract. You can’t draw a “Shape” — but you can draw a Circle, Rectangle, or Triangle that all follow the Shape contract.


Pure Virtual Functions: The Key Ingredient

A pure virtual function is declared with = 0 at the end:

virtual void draw() = 0;

This tells the compiler: “every subclass must implement this.” Any class containing at least one pure virtual function is automatically an abstract class.

#include <iostream>
using namespace std;

class Shape {
public:
    // Pure virtual function — subclasses must implement this
    virtual double area() = 0;

    // Regular function — shared by all shapes
    void describe() {
        cout << "I am a shape with area: " << area() << endl;
    }
};

int main() {
    // Shape s;  // ERROR: cannot instantiate abstract class
    return 0;
}

Trying to create a Shape directly causes a compile error. You must subclass it.


Implementing an Abstract Class

Subclasses provide the concrete implementations:

#include <iostream>
#include <cmath>
using namespace std;

class Shape {
public:
    virtual double area() = 0;
    virtual void draw() = 0;

    void describe() {
        cout << "Area: " << area() << endl;
    }
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}

    double area() override {
        return 3.14159 * radius * radius;
    }

    void draw() override {
        cout << "Drawing a circle with radius " << radius << endl;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() override {
        return width * height;
    }

    void draw() override {
        cout << "Drawing a " << width << "x" << height << " rectangle" << endl;
    }
};

int main() {
    Circle c(5.0);
    Rectangle r(4.0, 6.0);

    c.draw();
    c.describe();

    r.draw();
    r.describe();

    return 0;
}

Output:

Drawing a circle with radius 5
Area: 78.5397
Drawing a 4x6 rectangle
Area: 24

Why This Is Powerful: Polymorphism

The real benefit shows up when you use base class pointers. You can write code that works with any shape without knowing the specific type:

#include <iostream>
#include <vector>
using namespace std;

// ... (Shape, Circle, Rectangle from above)

int main() {
    vector<Shape*> shapes;
    shapes.push_back(new Circle(3.0));
    shapes.push_back(new Rectangle(4.0, 5.0));
    shapes.push_back(new Circle(7.0));

    // Works for every shape, regardless of type
    for (Shape* s : shapes) {
        s->draw();
        cout << "Area: " << s->area() << endl;
    }

    // Clean up
    for (Shape* s : shapes) delete s;

    return 0;
}

The draw() and area() calls resolve to the right implementation at runtime — this is polymorphism. Your loop doesn’t need to know whether it’s a Circle or Rectangle.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

Abstract Classes vs Interfaces

C++ doesn’t have a dedicated interface keyword like Java or C#. An “interface” in C++ is just an abstract class where every method is pure virtual:

// This is as close to an interface as C++ gets
class Printable {
public:
    virtual void print() = 0;
    virtual ~Printable() = default;
};

class Serializable {
public:
    virtual string serialize() = 0;
    virtual ~Serializable() = default;
};

// A class can implement multiple "interfaces"
class Document : public Printable, public Serializable {
private:
    string content;
public:
    Document(string c) : content(c) {}

    void print() override {
        cout << content << endl;
    }

    string serialize() override {
        return "{\"content\": \"" + content + "\"}";
    }
};

The Virtual Destructor Rule

Whenever you have a base class with virtual functions, always declare a virtual destructor:

class Shape {
public:
    virtual double area() = 0;
    virtual ~Shape() = default;  // Virtual destructor!
};

Without it, deleting a derived class object through a base pointer causes undefined behaviour — the derived class destructor won’t be called.


Partially Abstract Classes

A class can mix pure virtual functions with concrete ones. Concrete methods provide default behaviour; pure virtual methods force customization:

class Animal {
public:
    // Must override
    virtual string sound() = 0;

    // Shared behaviour — no need to override
    void breathe() {
        cout << "Inhale... exhale..." << endl;
    }

    void introduce() {
        cout << "I am an animal. I say: " << sound() << endl;
    }
};

class Dog : public Animal {
public:
    string sound() override { return "Woof"; }
};

Dog only needs to implement sound() — it inherits breathe() and introduce() for free.



Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19

📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Edit page
Share this post on:

Next Post
C++ Bitwise Operators Explained: AND, OR, XOR, Shift, and NOT for Beginners