C++ and Object-Oriented Programming

Pure Virtual Functions and Abstract Classes; Templates; Exception Handling

C-CAT

Pure Virtual Functions and Abstract Classes

Pure Virtual Function

A pure virtual function has no implementation — derived classes MUST override it.

class AbstractShape {
public:
    // Pure virtual function — no implementation
    virtual double area() = 0;        // = 0 makes it pure virtual
    virtual double perimeter() = 0;   // pure virtual

    // Non-virtual function — shared implementation
    void display() {
        cout << "Area: " << area() 
             << ", Perimeter: " << perimeter() << endl;
    }

    virtual ~AbstractShape() {}
};

Abstract class: A class with at least one pure virtual function.

  • Cannot be instantiated — you cannot create objects of abstract class
  • Can have pointers and references to abstract class type
  • Derived classes must override ALL pure virtual functions (or be abstract too)
// AbstractShape s;         // ERROR: cannot create object of abstract class
AbstractShape *s = new Circle(5.0);  // OK: pointer to abstract class
s->display();               // Uses Circle's area() and perimeter()

Interface Pattern

// Interface: pure abstract class (all functions pure virtual)
class Printable {
public:
    virtual void print() = 0;
    virtual ~Printable() {}
};

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

// 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 + "\"}"; }
};

Templates

What are Templates?

Templates allow writing generic code that works with any data type.

Function Templates

// Generic max function — works for any comparable type
template <typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << maxOf(3, 7) << endl;          // 7 (int comparison)
    cout << maxOf(3.14, 2.71) << endl;    // 3.14 (double comparison)
    cout << maxOf('a', 'z') << endl;      // z (char comparison)
    cout << maxOf(string("apple"), string("banana")) << endl; // banana
    return 0;
}

Class Templates

template <typename T>
class Stack {
private:
    T arr[100];
    int top;

public:
    Stack() : top(-1) {}

    void push(T value) {
        if (top == 99) { cout << "Stack overflow!\n"; return; }
        arr[++top] = value;
    }

    T pop() {
        if (top == -1) { throw runtime_error("Stack empty"); }
        return arr[top--];
    }

    T peek() { return arr[top]; }
    bool isEmpty() { return top == -1; }
};

int main() {
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    cout
<< intStack.pop() << endl;    // 20

    Stack<string> strStack;
    strStack.push("Hello");
    strStack.push("World");
    cout << strStack.pop() << endl;    // World

    return 0;
}

Multiple Template Parameters

template <typename Key, typename Value>
class Dictionary {
    // ...
};

Dictionary<string, int> wordCount;
Dictionary<int, string> idLookup;

Exception Handling

What is Exception Handling?

Exception handling manages runtime errors gracefully without crashing the program.

Three keywords:

  • try — block of code that might throw an exception
  • throw — throws an exception
  • catch — handles the exception
#include <iostream>
#include <stdexcept>
using namespace std;

double divide(double a, double b) {
    if (b == 0) {
        throw runtime_error("Division
by zero!");   // throw exception
    }
    return a / b;
}

int main() {
    try {
        cout << divide(10, 2) << endl;    // 5.0 — works fine
        cout << divide(10, 0) << endl;    // throws exception!
    }
    catch (runtime_error &e) {
        cout << "Error: " << e.what() << endl;   // Error: Division by zero!
    }
    catch (exception &e) {
        cout << "General error: " << e.what() << endl;
    }
    catch (...) {
        cout << "Unknown exception caught\n";
    }

    cout << "Program continues after exception\n";
    return 0;
}

Custom Exceptions

class InsufficientFundsException : public exception {
private:
    double amount;
public:
    InsufficientFundsException(double a) : amount(a) {}

    const char* what() const noexcept override {
        return "Insufficient funds in account";
    }

    double getAmount() { return amount; }
};

class BankAccount {
private:
    double balance;
public:
    BankAccount(double initial) :
balance(initial) {}

    void withdraw(double amount) {
        if (amount > balance) {
throw InsufficientFundsException(amount - balance);
        }
        balance -= amount;
}
};

int main() {
    BankAccount acc(1000.0);
    try {
        acc.withdraw(500.0);    // OK
        acc.withdraw(600.0);    // throws InsufficientFundsException
    }
    catch (InsufficientFundsException &e) {
        cout << e.what() << " (short by " << e.getAmount() << ")\n";
    }
    return 0;
}

Exception Hierarchy

std::exception
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::out_of_range
│   └── std::length_error
└── std::runtime_error
    ├── std::overflow_error
    ├── std::underflow_error
    └── std::range_error

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.