Abstract Class in C++

What is an Abstract Class?

An abstract class is a class that cannot be instantiated on its own and is designed to be inherited by other classes. It serves as a blueprint for other classes and may contain abstract methods (methods without implementation) that derived classes must implement.

Purpose of Abstract Classes

Abstract classes are used for:

  1. Creating a common base class that provides a partial implementation
  2. Enforcing that derived classes implement specific methods
  3. Defining interfaces that derived classes must follow
  4. Providing a clear structure for hierarchical class relationships

How to Create an Abstract Class

In C++, a class becomes abstract when it has at least one pure virtual function. A pure virtual function is declared by using = 0 at the end of its declaration:

class AbstractClass {
public:
    // Pure virtual function makes this an abstract class
    virtual void pureVirtualFunction() = 0;
    
    // Regular function with implementation
    void regularFunction() {
        cout << "This is a regular function" << endl;
    }
};

Pure Virtual Functions

A pure virtual function:

  • Is declared with the virtual keyword
  • Has = 0 at the end of its declaration
  • Has no implementation in the abstract class
  • Must be implemented by any non-abstract derived class
class Shape {
public:
    // Pure virtual function
    virtual double area() = 0;
    
    // Pure virtual function
    virtual double perimeter() = 0;
    
    // Regular virtual function with implementation
    virtual void display() {
        cout << "This is a shape" << endl;
    }
};

Basic Example of an Abstract Class

#include <iostream>
using namespace std;

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

// Concrete derived class
class Rectangle : public Shape {
private:
    double length;
    double width;
    
public:
    Rectangle(double l, double w) : length(l), width(w) {}
    
    // Implementation of pure virtual function
    double area() override {
        return length * width;
    }
    
    // Implementation of pure virtual function
    double perimeter() override {
        return 2 * (length + width);
    }
};

// Another concrete derived class
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    // Implementation of pure virtual function
    double area() override {
        return 3.14159 * radius * radius;
    }
    
    // Implementation of pure virtual function
    double perimeter() override {
        return 2 * 3.14159 * radius;
    }
};

int main() {
    // Shape shape;  // Error: Cannot create an instance of an abstract class
    
    Rectangle rect(5.0, 3.0);
    Circle circle(4.0);
    
    cout << "Rectangle information:" << endl;
    rect.displayInfo();
    
    cout << "\nCircle information:" << endl;
    circle.displayInfo();
    
    // Using pointers to the abstract class
    Shape* shape1 = &rect;
    Shape* shape2 = &circle;
    
    cout << "\nUsing pointers:" << endl;
    cout << "Shape1 area: " << shape1->area() << endl;
    cout << "Shape2 area: " << shape2->area() << endl;
    
    return 0;
}

Output:

Rectangle information:
Area: 15
Perimeter: 16

Circle information:
Area: 50.2654
Perimeter: 25.1327

Using pointers:
Shape1 area: 15
Shape2 area: 50.2654

Key Characteristics of Abstract Classes

  1. Cannot be instantiated: You cannot create objects of an abstract class
  2. May contain pure virtual functions: Methods that derived classes must implement
  3. May contain regular functions: Methods with implementations that derived classes can use
  4. Can have constructors: Even though you can’t create objects directly
  5. Can have data members: Variables that derived classes inherit
  6. Can have pointers and references: You can create pointers and references to abstract classes

Abstract Class vs. Interface

In C++, there is no explicit “interface” keyword as in some other languages like Java or C#. Instead, abstract classes with only pure virtual functions serve as interfaces:

// This abstract class acts as an interface
class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize() = 0;
    
    // Virtual destructor
    virtual ~Drawable() {}
};

The differences between a typical abstract class and an “interface-like” abstract class are:

  1. Abstract class:

    • May have implemented methods
    • May have member variables
    • Derived classes can only inherit from one abstract class (C++ limitation)
  2. Interface-like abstract class:

    • Contains only pure virtual functions
    • Usually has no member variables
    • Multiple interface-like classes can be inherited by a single class

Constructors in Abstract Classes

Even though you cannot create objects of an abstract class directly, abstract classes can have constructors. These constructors are called when a derived class object is created:

#include <iostream>
using namespace std;

class AbstractBase {
protected:
    int value;
    
public:
    // Constructor in abstract class
    AbstractBase(int v) : value(v) {
        cout << "AbstractBase constructor called with value " << value << endl;
    }
    
    // Pure virtual function
    virtual void display() = 0;
    
    // Virtual destructor
    virtual ~AbstractBase() {
        cout << "AbstractBase destructor called" << endl;
    }
};

class Derived : public AbstractBase {
public:
    // Call the base class constructor
    Derived(int v) : AbstractBase(v) {
        cout << "Derived constructor called" << endl;
    }
    
    // Implement the pure virtual function
    void display() override {
        cout << "Value in Derived: " << value << endl;
    }
    
    ~Derived() {
        cout << "Derived destructor called" << endl;
    }
};

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

Output:

AbstractBase constructor called with value 42
Derived constructor called
Value in Derived: 42
Derived destructor called
AbstractBase destructor called

Abstract Class Pointers and References

While you cannot create objects of an abstract class, you can create pointers and references to abstract classes. These pointers and references can then point to or refer to objects of derived classes:

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

class Animal {
public:
    virtual void makeSound() = 0;
    
    virtual void eat() {
        cout << "Animal is eating" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Woof!" << endl;
    }
    
    void eat() override {
        cout << "Dog is eating bones" << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        cout << "Meow!" << endl;
    }
    
    void eat() override {
        cout << "Cat is eating fish" << endl;
    }
};

void animalSound(Animal& animal) {
    animal.makeSound();
}

int main() {
    // Animal animal;  // Error: Cannot instantiate abstract class
    
    Dog dog;
    Cat cat;
    
    // Using abstract class reference
    animalSound(dog);  // Output: Woof!
    animalSound(cat);  // Output: Meow!
    
    // Using abstract class pointers
    Animal* animals[2];
    animals[0] = &dog;
    animals[1] = &cat;
    
    for (int i = 0; i < 2; i++) {
        animals[i]->makeSound();
        animals[i]->eat();
    }
    
    // Using abstract class pointers with vector
    vector<Animal*> animalVector;
    animalVector.push_back(&dog);
    animalVector.push_back(&cat);
    
    for (Animal* a : animalVector) {
        a->makeSound();
    }
    
    return 0;
}

Output:

Woof!
Meow!
Woof!
Dog is eating bones
Meow!
Cat is eating fish
Woof!
Meow!

Abstract Classes as Interfaces

In C++, abstract classes are often used to define interfaces that derived classes must implement:

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

// Database interface (abstract class)
class Database {
public:
    virtual bool connect(const string& connectionString) = 0;
    virtual bool disconnect() = 0;
    virtual bool executeQuery(const string& query) = 0;
    virtual ~Database() {}
};

// MySQL implementation
class MySQLDatabase : public Database {
public:
    bool connect(const string& connectionString) override {
        cout << "Connecting to MySQL database: " << connectionString << endl;
        return true;
    }
    
    bool disconnect() override {
        cout << "Disconnecting from MySQL database" << endl;
        return true;
    }
    
    bool executeQuery(const string& query) override {
        cout << "Executing MySQL query: " << query << endl;
        return true;
    }
};

// PostgreSQL implementation
class PostgreSQLDatabase : public Database {
public:
    bool connect(const string& connectionString) override {
        cout << "Connecting to PostgreSQL database: " << connectionString << endl;
        return true;
    }
    
    bool disconnect() override {
        cout << "Disconnecting from PostgreSQL database" << endl;
        return true;
    }
    
    bool executeQuery(const string& query) override {
        cout << "Executing PostgreSQL query: " << query << endl;
        return true;
    }
};

// Database client that works with any database implementation
class DatabaseClient {
private:
    Database* db;
    
public:
    DatabaseClient(Database* database) : db(database) {}
    
    void performOperation() {
        db->connect("server:1234");
        db->executeQuery("SELECT * FROM users");
        db->disconnect();
    }
};

int main() {
    MySQLDatabase mysqlDb;
    PostgreSQLDatabase postgresDb;
    
    // Client works with MySQL
    DatabaseClient client1(&mysqlDb);
    cout << "Working with MySQL:" << endl;
    client1.performOperation();
    
    cout << "\nWorking with PostgreSQL:" << endl;
    // Same client works with PostgreSQL
    DatabaseClient client2(&postgresDb);
    client2.performOperation();
    
    return 0;
}

Output:

Working with MySQL:
Connecting to MySQL database: server:1234
Executing MySQL query: SELECT * FROM users
Disconnecting from MySQL database

Working with PostgreSQL:
Connecting to PostgreSQL database: server:1234
Executing PostgreSQL query: SELECT * FROM users
Disconnecting from PostgreSQL database

Multiple Abstract Base Classes

A class can inherit from multiple abstract base classes, implementing all their pure virtual functions:

#include <iostream>
using namespace std;

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

// Second abstract base class
class Movable {
public:
    virtual void move(int x, int y) = 0;
    virtual ~Movable() {}
};

// Third abstract base class
class Resizable {
public:
    virtual void resize(int width, int height) = 0;
    virtual ~Resizable() {}
};

// Class implementing multiple abstract base classes
class Rectangle : public Drawable, public Movable, public Resizable {
private:
    int x, y, width, height;
    
public:
    Rectangle(int x, int y, int w, int h) : x(x), y(y), width(w), height(h) {}
    
    // Implement Drawable interface
    void draw() override {
        cout << "Drawing rectangle at (" << x << "," << y << ") "
             << "with width " << width << " and height " << height << endl;
    }
    
    // Implement Movable interface
    void move(int newX, int newY) override {
        x = newX;
        y = newY;
        cout << "Moved rectangle to (" << x << "," << y << ")" << endl;
    }
    
    // Implement Resizable interface
    void resize(int newWidth, int newHeight) override {
        width = newWidth;
        height = newHeight;
        cout << "Resized rectangle to width " << width 
             << " and height " << height << endl;
    }
};

int main() {
    Rectangle rect(10, 20, 30, 40);
    
    // Use object directly
    rect.draw();
    rect.move(15, 25);
    rect.resize(50, 60);
    rect.draw();
    
    // Use through interfaces
    Drawable* d = &rect;
    Movable* m = &rect;
    Resizable* r = &rect;
    
    d->draw();
    m->move(100, 100);
    r->resize(200, 200);
    d->draw();
    
    return 0;
}

Common Mistakes with Abstract Classes

  1. Forgetting to implement a pure virtual function:

    class Derived : public AbstractBase {
        // Error: Derived is still abstract because it doesn't implement all pure virtual functions
    };
  2. Trying to instantiate an abstract class:

    AbstractBase obj;  // Error: Cannot instantiate abstract class
  3. Not making destructors virtual:

    class AbstractBase {
    public:
        virtual void func() = 0;
        ~AbstractBase() {}  // Should be virtual
    };
  4. Forgetting to call the abstract class constructor:

    class Derived : public AbstractBase {
    public:
        Derived() {  // Should call AbstractBase constructor if it requires parameters
            // ...
        }
    };

Best Practices for Abstract Classes

  1. Make destructors virtual: Always make destructors virtual in abstract classes
  2. Use pure virtual functions for interface definition: Use pure virtual functions for methods that derived classes must implement
  3. Provide default implementations when appropriate: Use regular virtual functions for methods that have a common implementation
  4. Use abstract classes for common behavior: Group related functionality in abstract classes
  5. Keep interfaces small and focused: Abstract classes work best when they represent a single, well-defined concept
  6. Use multiple inheritance carefully: Inherit from multiple abstract classes only when necessary

Summary

Abstract classes in C++ provide a powerful way to define common interfaces and partial implementations that derived classes must follow and can extend. By using pure virtual functions, abstract classes ensure that concrete derived classes implement the required functionality. They serve as blueprints for derived classes and enable polymorphic behavior through base class pointers and references. Understanding how to design and use abstract classes effectively is essential for creating flexible, maintainable, and extensible object-oriented C++ code.