Pure Virtual Functions

Pure virtual functions are a special type of virtual function that have no implementation in the base class and must be implemented by any derived class that wants to be instantiated. They are a key feature in C++ for creating abstract classes and implementing interfaces.

What is a Pure Virtual Function?

A pure virtual function is a virtual function that has no implementation in the base class and is set to 0. The syntax is:

virtual return_type function_name(parameters) = 0;

For example:

class Shape {
public:
    virtual double area() = 0;  // Pure virtual function
};

Abstract Classes

A class that contains at least one pure virtual function is called an abstract class. Key characteristics of abstract classes include:

  1. Cannot be instantiated directly: You cannot create objects of an abstract class.
  2. Used as base classes: They serve as interfaces for derived classes.
  3. Must be implemented: Any derived class must implement all the pure virtual functions to be instantiable.
  4. Can have normal methods: Abstract classes can still have data members and normal (non-pure virtual) methods.
#include <iostream>
using namespace std;

// Abstract class
class Shape {
public:
    // Pure virtual functions
    virtual double area() = 0;
    virtual double perimeter() = 0;
    
    // Normal method
    void printInfo() {
        cout << "Area: " << area() << endl;
        cout << "Perimeter: " << perimeter() << endl;
    }
};

// Concrete class
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14159 * radius * radius;
    }
    
    double perimeter() override {
        return 2 * 3.14159 * radius;
    }
};

int main() {
    // Shape shape;  // Error: Cannot instantiate an abstract class
    
    Circle circle(5);
    circle.printInfo();
    
    Shape* shapePtr = &circle;  // Valid: Can point to derived class
    shapePtr->printInfo();
    
    return 0;
}

Output:

Area: 78.5397
Perimeter: 31.4159
Area: 78.5397
Perimeter: 31.4159

Difference Between Virtual and Pure Virtual Functions

Virtual FunctionsPure Virtual Functions
Declared with virtual keywordDeclared with virtual keyword and = 0
Have implementation in the base classNo implementation in the base class (must be overridden)
Base class is not necessarily abstractMake the base class abstract
Derived classes can choose to overrideDerived classes must implement to be instantiable
Can be called directlyCannot be called without an implementation

Interface in C++

C++ doesn’t have a dedicated interface keyword like Java or C#, but pure virtual functions are used to create interface-like abstract classes. An interface in C++ is typically an abstract class with:

  1. Only pure virtual functions
  2. No data members
  3. No constructors (except a virtual destructor)
// Interface
class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize() = 0;
    virtual ~Drawable() {}  // Virtual destructor
};

// Another interface
class Printable {
public:
    virtual void print() = 0;
    virtual ~Printable() {}  // Virtual destructor
};

// Class implementing multiple interfaces
class Picture : public Drawable, public Printable {
public:
    void draw() override {
        cout << "Drawing a picture" << endl;
    }
    
    void resize() override {
        cout << "Resizing a picture" << endl;
    }
    
    void print() override {
        cout << "Printing a picture" << endl;
    }
};

Implementing Pure Virtual Functions in the Base Class

Although unusual, you can provide an implementation for a pure virtual function in the base class. The class is still abstract, but derived classes can call the base implementation:

class Base {
public:
    virtual void show() = 0;  // Pure virtual
};

// Implementation outside the class
void Base::show() {
    cout << "Base implementation of pure virtual function" << endl;
}

class Derived : public Base {
public:
    void show() override {
        // Call the base implementation
        Base::show();
        cout << "Derived implementation" << endl;
    }
};

int main() {
    Derived d;
    d.show();
    return 0;
}

Output:

Base implementation of pure virtual function
Derived implementation

When to Use Pure Virtual Functions

Use pure virtual functions when:

  1. You want to define an interface that derived classes must implement.
  2. The base class doesn’t have a meaningful implementation for a method.
  3. You want to force derived classes to provide an implementation.
  4. You want to prevent instantiation of the base class.

Real-World Example: Shape Hierarchy

One of the most common examples of pure virtual functions is a shape hierarchy:

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

// Abstract base class
class Shape {
public:
    // Pure virtual functions
    virtual double area() = 0;
    virtual double perimeter() = 0;
    virtual void draw() = 0;
    
    // Normal method
    void printInfo() {
        cout << "Shape Info:" << endl;
        cout << "- Area: " << area() << endl;
        cout << "- Perimeter: " << perimeter() << endl;
        draw();
        cout << endl;
    }
    
    // Virtual destructor
    virtual ~Shape() {}
};

// Concrete class: Circle
class Circle : public Shape {
private:
    double radius;
    double x, y;  // Center coordinates
    
public:
    Circle(double r, double cx, double cy) : radius(r), x(cx), y(cy) {}
    
    double area() override {
        return M_PI * radius * radius;
    }
    
    double perimeter() override {
        return 2 * M_PI * radius;
    }
    
    void draw() override {
        cout << "- Drawing Circle at (" << x << ", " << y << ") with radius " << radius << endl;
    }
};

// Concrete class: Rectangle
class Rectangle : public Shape {
private:
    double length;
    double width;
    double x, y;  // Top-left corner coordinates
    
public:
    Rectangle(double l, double w, double cx, double cy) : length(l), width(w), x(cx), y(cy) {}
    
    double area() override {
        return length * width;
    }
    
    double perimeter() override {
        return 2 * (length + width);
    }
    
    void draw() override {
        cout << "- Drawing Rectangle at (" << x << ", " << y << ") with length " 
             << length << " and width " << width << endl;
    }
};

// Concrete class: Triangle
class Triangle : public Shape {
private:
    double a, b, c;  // Sides
    double x, y;     // Position
    
public:
    Triangle(double side1, double side2, double side3, double px, double py) 
        : a(side1), b(side2), c(side3), x(px), y(py) {}
    
    double area() override {
        // Using Heron's formula
        double s = (a + b + c) / 2;
        return sqrt(s * (s - a) * (s - b) * (s - c));
    }
    
    double perimeter() override {
        return a + b + c;
    }
    
    void draw() override {
        cout << "- Drawing Triangle at (" << x << ", " << y << ") with sides " 
             << a << ", " << b << ", " << c << endl;
    }
};

int main() {
    vector<Shape*> shapes;
    
    shapes.push_back(new Circle(5, 0, 0));
    shapes.push_back(new Rectangle(4, 6, 10, 10));
    shapes.push_back(new Triangle(3, 4, 5, 20, 20));
    
    for (const auto& shape : shapes) {
        shape->printInfo();
    }
    
    // Clean up memory
    for (auto& shape : shapes) {
        delete shape;
    }
    
    return 0;
}

Output:

Shape Info:
- Area: 78.5398
- Perimeter: 31.4159
- Drawing Circle at (0, 0) with radius 5

Shape Info:
- Area: 24
- Perimeter: 20
- Drawing Rectangle at (10, 10) with length 4 and width 6

Shape Info:
- Area: 6
- Perimeter: 12
- Drawing Triangle at (20, 20) with sides 3, 4, 5

Abstract Classes vs. Interfaces

In object-oriented design, there’s a distinction between abstract classes and interfaces:

  1. Abstract Class:

    • May contain both implemented methods and pure virtual methods
    • May have data members
    • Represents an “is-a” relationship
    • Example: Shape with some common functionality
  2. Interface (in C++, an abstract class with only pure virtual functions):

    • Contains only pure virtual methods (no implementation)
    • No data members
    • Represents a “can-do” relationship
    • Example: Drawable or Printable

Multiple Inheritance with Pure Virtual Functions

C++ allows multiple inheritance, which can be very powerful when combined with abstract classes:

#include <iostream>
using namespace std;

// First interface
class Drawable {
public:
    virtual void draw() = 0;
    virtual ~Drawable() {}
};

// Second interface
class Storable {
public:
    virtual void save() = 0;
    virtual void load() = 0;
    virtual ~Storable() {}
};

// Concrete class implementing both interfaces
class Document : public Drawable, public Storable {
private:
    string name;
    
public:
    Document(const string& n) : name(n) {}
    
    void draw() override {
        cout << "Drawing document: " << name << endl;
    }
    
    void save() override {
        cout << "Saving document: " << name << endl;
    }
    
    void load() override {
        cout << "Loading document: " << name << endl;
    }
};

int main() {
    Document doc("Report.pdf");
    
    // Use as Drawable
    Drawable* d = &doc;
    d->draw();
    
    // Use as Storable
    Storable* s = &doc;
    s->save();
    s->load();
    
    return 0;
}

Output:

Drawing document: Report.pdf
Saving document: Report.pdf
Loading document: Report.pdf

Common Design Patterns Using Pure Virtual Functions

  1. Strategy Pattern: Define a family of algorithms, encapsulate each one, and make them interchangeable.
// Strategy interface
class SortStrategy {
public:
    virtual void sort(int arr[], int size) = 0;
    virtual ~SortStrategy() {}
};

// Concrete strategies
class BubbleSort : public SortStrategy {
public:
    void sort(int arr[], int size) override {
        cout << "Sorting array using bubble sort" << endl;
        // Bubble sort implementation...
    }
};

class QuickSort : public SortStrategy {
public:
    void sort(int arr[], int size) override {
        cout << "Sorting array using quick sort" << endl;
        // Quick sort implementation...
    }
};

// Context
class Sorter {
private:
    SortStrategy* strategy;
    
public:
    Sorter(SortStrategy* s) : strategy(s) {}
    
    void setStrategy(SortStrategy* s) {
        strategy = s;
    }
    
    void performSort(int arr[], int size) {
        strategy->sort(arr, size);
    }
};
  1. Observer Pattern: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
// Observer interface
class Observer {
public:
    virtual void update(const string& message) = 0;
    virtual ~Observer() {}
};

// Subject
class Subject {
private:
    vector<Observer*> observers;
    
public:
    void addObserver(Observer* obs) {
        observers.push_back(obs);
    }
    
    void removeObserver(Observer* obs) {
        // Remove logic...
    }
    
    void notifyObservers(const string& message) {
        for (auto& obs : observers) {
            obs->update(message);
        }
    }
};

// Concrete observer
class ConcreteObserver : public Observer {
private:
    string name;
    
public:
    ConcreteObserver(const string& n) : name(n) {}
    
    void update(const string& message) override {
        cout << name << " received: " << message << endl;
    }
};

Best Practices for Pure Virtual Functions

  1. Use virtual destructors: If your class has any virtual functions, always make the destructor virtual.

  2. Pure virtual destructors: You can have a pure virtual destructor, but you must provide an implementation.

class Base {
public:
    virtual ~Base() = 0;  // Pure virtual destructor
};

Base::~Base() {
    // Implementation is required
    cout << "Base destructor" << endl;
}
  1. Use override keyword: Always use the override keyword when implementing pure virtual functions to catch errors at compile time.

  2. Keep interfaces small: Design interfaces with the minimum number of methods needed. This follows the Interface Segregation Principle.

  3. Consider default implementations: If a method will be implemented the same way in most derived classes, consider making it virtual (not pure) with a default implementation.

Summary

Pure virtual functions are a powerful feature in C++ that allow you to:

  1. Create abstract base classes that can’t be instantiated
  2. Define interfaces that derived classes must implement
  3. Build flexible class hierarchies
  4. Enable runtime polymorphism

By using pure virtual functions, you can design clean, extendable, and maintainable object-oriented systems where the base classes define the interface, and the derived classes provide the specific implementations.