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.
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.
Related Articles
- Virtual Functions and Polymorphism in C++
- C++ Inheritance Explained
- C++ Classes and Objects: A Beginner’s Guide
- OOP in C++: The Four Pillars Explained
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.