Skip to main content

Object-Oriented Programming (OOP)

C++ is a multi-paradigm language, but its OOP features are powerful. OOP allows you to model real-world entities (like a Player, Car, or File) as objects that contain both data and behavior.

1. Classes & Structs

A Class is a blueprint for creating objects. It encapsulates data (attributes) and functions (methods) that operate on that data. In C++, class and struct are almost identical. The only difference is default visibility:
  • class: Members are private by default. (Used for complex objects with invariants).
  • struct: Members are public by default. (Used for simple data containers).
class Player {
private:
    // Data (State) - Hidden from the outside world (Encapsulation)
    std::string name;
    int health;

public:
    // Constructor: Initializes the object
    Player(std::string n, int h) : name(n), health(h) {
        // Member Initializer List (Preferred over assignment inside body)
    }

    // Method: Defines behavior
    void takeDamage(int amount) {
        health -= amount;
    }

    // Getter: Provides read-only access
    // 'const' means this method will NOT modify the object
    int getHealth() const {
        return health;
    }
};

Access Modifiers

  • public: Accessible from anywhere. The “interface” of your class.
  • private: Accessible only from within the class. Used for internal state.
  • protected: Accessible from the class and its derived classes (children).

2. Inheritance

Inheritance allows a class to derive properties and behavior from another. It promotes code reuse.
// Base Class (Parent)
class Character {
protected:
    int health;
public:
    Character(int h) : health(h) {}
    
    // 'virtual' allows this function to be overridden by children
    virtual void attack() { std::cout << "Generic attack\n"; }
};

// Derived Class (Child)
class Wizard : public Character {
    int mana;
public:
    Wizard(int h, int m) : Character(h), mana(m) {}

    // Override base behavior
    void attack() override {
        std::cout << "Cast fireball! Mana: " << mana << "\n";
    }
};
Always use the override keyword when overriding virtual functions. It ensures the compiler checks that you are actually overriding a base method, preventing bugs from typos.

3. Polymorphism

Polymorphism (“many shapes”) allows you to treat derived objects as base objects. This is the magic that lets you write flexible code. For example, you can have a list of Character* pointers, some pointing to Wizards, some to Warriors. When you call attack(), the correct version is called for each object.
void performAttack(Character* c) {
    // Dynamic Dispatch: The runtime decides which function to call
    c->attack(); 
}

int main() {
    Wizard w(100, 50);
    Character c(100);

    performAttack(&w); // Prints "Cast fireball..."
    performAttack(&c); // Prints "Generic attack"
}

Virtual Destructors

Crucial: If a class has virtual functions, it must have a virtual destructor. This ensures that when you delete a base pointer, the child’s destructor is called, cleaning up its resources.
class Base {
public:
    virtual ~Base() { std::cout << "Base destroyed\n"; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived destroyed\n"; }
};

// If ~Base() wasn't virtual:
Base* b = new Derived();
delete b; // Only ~Base() would run, leaking Derived's resources!

4. Abstract Classes & Interfaces

A class is abstract if it has at least one pure virtual function (= 0). It cannot be instantiated. This is how C++ implements interfaces. It forces children to implement specific behavior.
class IDrawable {
public:
    virtual void draw() const = 0; // Pure virtual: "Children MUST implement this"
    virtual ~IDrawable() = default;
};

class Circle : public IDrawable {
public:
    void draw() const override {
        std::cout << "Drawing Circle\n";
    }
};

5. The Rule of Five

In modern C++, if you manage resources manually (like raw pointers), you need to define 5 special member functions to handle copying and moving correctly.
  1. Destructor: Cleans up resources.
  2. Copy Constructor: Creates a new object from an existing one (Deep Copy).
  3. Copy Assignment Operator: Assigns an existing object to another (Deep Copy).
  4. Move Constructor (C++11): “Steals” resources from a temporary object (Performance).
  5. Move Assignment Operator (C++11): Assigns by stealing resources.
class Buffer {
    int* data;
    size_t size;

public:
    // 1. Destructor
    ~Buffer() { delete[] data; }

    // 2. Copy Constructor (Deep Copy)
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
    }

    // ... (Assignment operators omitted for brevity)
};
Rule of Zero: If your class only uses RAII types (like std::string, std::vector, std::unique_ptr), you don’t need to write ANY of these. The compiler defaults work perfectly. This is the goal.

Summary

  • Classes: Encapsulate data and behavior.
  • Inheritance: Enables code reuse (public inheritance is “is-a”).
  • Polymorphism: Allows dynamic behavior via Virtual Functions.
  • Abstract Classes: Define interfaces/contracts.
  • Rule of Five: Implement copy/move semantics only if managing raw resources. Otherwise, follow Rule of Zero.
Next, we’ll explore the Standard Template Library (STL), which provides powerful containers and algorithms so you don’t have to write them from scratch.