CODE: C CPP Object Oriented Programming (OOP)

Object-Oriented Programming in C++ is not about syntax—it is about modeling reality, controlling complexity, and enforcing correctness over time.

C++ did not adopt OOP for elegance—it adopted it for scale and correctness.

You use OOP when:

  • Multiple components share common behavior
  • You want to separate interface from implementation
  • You need runtime substitution (polymorphism)
  • You want to extend behavior without rewriting existing code

Inheritance is the backbone that enables all of this—but it must be used deliberately, not mechanically.

Inheritance (`:`): Modeling an _Is-A_ Relationship

Inheritance allows a derived class to reuse and extend a base class.

md
        +------------------+
        |     BaseClass    |
        |------------------|
        | + baseNumber     |
        | + baseMethod()   |
        +---------^--------+
                  |
          public inheritance
                  |
        +------------------+
        |   DerivedClass   |
        |------------------|
        | + derivedNumber  |
        | + derivedMethod()|
        +------------------+

cpp
class BaseClass {
public:
    int baseNumber;
};

class DerivedClass : public BaseClass {
public:
    int derivedNumber;
};

When to Use Inheritance

Use inheritance only when:

  • Derived is a Base (semantic relationship)
  • The base class represents a stable abstraction
  • You want polymorphic substitution

Do not use inheritance just to reuse code, Prefer composition unless substitution is required


Types of Inheritance: Controlling the Public Contract

Inheritance is not just about behavior—it controls visibility and guarantees.

cpp
class Derived : public Base {};

Meaning:

“A `Derived` _is a_ `Base`”
  • publicpublic
  • protectedprotected
  • private → not accessible

Use this for polymorphism and APIs


2. Protected Inheritance

cpp
class Derived : public Base {};

  • Public and protected members become protected
  • External users cannot treat Derived as Base

Rarely used, Useful in framework internals


3. Private Inheritance (Default)

cpp
class Derived : private Base {};

  • Public/protected become private
  • No “is-a” relationship

Not polymorphism, Implementation reuse only


Multiple & Multi-Level Inheritance

C++ supports:

  • Multi-level inheritance (A → B → C)
  • Multiple inheritance (C : A, B)

cpp
class A {};
class B {};
class C : public A, public B {};

When Multiple Inheritance Is Safe

  • One base provides interface only
  • Other provides implementation
  • Virtual inheritance is well understood

Otherwise: high complexity, ambiguity, diamond problems


Initialization Lists: Correct Construction Order

C++ constructs base classes before derived classes.

cpp
class Base {
    int value;
public:
    Base(int v) : value(v) {
        std::cout << "Base initialized: " << value << '\n';
    }
};

class Derived : public Base {
    int derivedValue;
public:
    Derived(int baseVal, int derivedVal)
        : Base(baseVal), derivedValue(derivedVal) {
        std::cout << "Derived initialized: " << derivedValue << '\n';
    }
};

Why Initialization Lists Matter

  • Base classes must be constructed first
  • Avoids default construction + reassignment
  • Required for:
  • const members
  • references
  • base classes without default constructors
Assigning inside constructor body is **incorrect design**

Constructor & Destructor Order (Critical for Resources)

Order is deterministic:

  • Construction: Base → Derived
  • Destruction: Derived → Base

md
Construction order:
-------------------
Base()
  |
  v
Derived()

Destruction order:
------------------
Derived()
  |
  v
Base()

cpp
class Base {
public:
    Base()  { std::cout << "Base constructor\n"; }
    ~Base() { std::cout << "Base destructor\n"; }
};

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

Output

bash
Base constructor
Derived constructor
Derived destructor
Base destructor

Why This Matters

  • Base resources must exist before derived uses them
  • Derived must clean up before base is destroyed

If a base class is used polymorphically, its destructor must be virtual


Function Overriding: Runtime Polymorphism

Overriding allows a derived class to replace behavior.

cpp
class Animal {
public:
    virtual void sound() {
        std::cout << "Animal makes a sound\n";
    }
};

class Dog : public Animal {
public:
    void sound() override {
        std::cout << "Dog barks\n";
    }
};

When Overriding Is Needed

  • Behavior depends on runtime type
  • You call functions via base pointers or references

md
   Animal
+-----------+
| sound()   |
+-----^-----+
	  |
+-----------+
|   Dog     |
| sound()   |
+-----------+

cpp
Animal* a = new Dog();
a->sound(); // Dog barks

Without virtualwrong function is called


`virtual`: Dynamic Dispatch Explained

virtual tells the compiler:

“Decide which function to call **at runtime**, not compile time.”

cpp
Base* obj = new Derived();
obj->speak(); // Calls Derived::speak()

What Happens Internally:

  • Object contains a vtable pointer
  • Function lookup happens at runtime
  • Small overhead, massive flexibility

Without virtual (Compile-time binding):

md
ptr ---> Base::speak()

With virtual (Runtime binding)

md
ptr
 |
 v
+------------------+
|   vtable ptr     |-----> Derived::speak()
+------------------+


Pure Virtual Functions & Abstract Classes (Interface Contract)

cpp
class Base {
public:
    virtual void speak() = 0;
};

  • = 0pure virtual
  • Class becomes abstract
  • Cannot be instantiated

Why abstract classes exist

To enforce _what must be implemented_, not _how_.

md
+------------------+
|   AbstractBase   |
|------------------|
| + speak() = 0    |
+---------^--------+
		  |
+------------------+
|     Derived      |
|------------------|
| + speak()        |
+------------------+

cpp
AbstractBase b;   // illegal
Derived d;        // legal

cpp
class Derived : public Base {
public:
    void speak() override {
        std::cout << "Derived speaking\n";
    }
};

When to Use Abstract Classes:

  • Define interfaces
  • Enforce implementation contracts
  • Enable framework-style extensibility

`final`: Locking Design Decisions

final prevents further modification.

1. Prevent Overriding

md
Base::foo()  [final]
     |
     X  override forbidden

cpp
class Base {
public:
    void foo() final;
};

2. Prevent Inheritance (No Inheritance)

cpp
class Utility final {};

md
class Utility final
        |
        X  cannot derive

Why `final` Exists

  • Enforce invariants
  • Improve optimization
  • Prevent unsafe extension
`virtual` and `final` **cannot be combined** on the same function

Design Rules You Should Remember

  • Use inheritance for polymorphism, not reuse
  • Always use override
  • Base destructors must be virtual
  • Prefer composition when substitution is not required
  • Use final to protect stable APIs

OOP and Performance: The Truth

  • Virtual calls cost one indirection
  • Cost is negligible compared to:
  • cache misses
  • allocations
  • synchronization
  • Wrong architecture costs far more

Correct OOP improves maintainability, safety, and scalability, Premature optimization breaks systems


Composition vs Inheritance in C++

Designing Correct, Maintainable, and Performant Systems

Object-oriented design in C++ is not about choosing _features_—it is about choosing relationships.

The most common and most expensive mistake is using inheritance where composition is the correct tool.

This article explains when and why to use composition or inheritance, using ASCII diagrams, real C++ examples, and performance-aware reasoning.


The Core Question

Before writing : public Base, always ask:

txt
Is this an "IS-A" relationship
or a "HAS-A" relationship?

  • IS-A → inheritance
  • HAS-A → composition

Most real systems are HAS-A.


Inheritance: Strong Coupling (IS-A)

What Inheritance Means

txt
Derived IS-A Base
Derived can replace Base

md
+------------------+
|     Vehicle      |
|------------------|
| + move()         |
+---------^--------+
		  |
public inheritance
		  |
+------------------+
|       Car        |
|------------------|
| + openTrunk()    |
+------------------+

C++ Example

cpp
class Vehicle {
public:
    virtual void move() {
        std::cout << "Vehicle moves\n";
    }
};

class Car : public Vehicle {
public:
    void move() override {
        std::cout << "Car drives\n";
    }
};

When Inheritance Is Correct

  • You need runtime polymorphism
  • The base class is a stable abstraction
  • Substitution must be valid everywhere

cpp
void transport(Vehicle& v) {
    v.move();
}

Passing Car here must always be correct.


The Hidden Cost of Inheritance

Inheritance creates tight coupling.

Derived depends on:

  • Base implementation
  • Base lifetime rules
  • Base virtual layout
  • Base future changes

Fragile Base Class Problem

txt
Base changes
   |
   v
Derived breaks silently

This is why inheritance hierarchies rot over time.


Composition: Flexible Design (HAS-A)

What Composition Means

Object CONTAINS another object, Behavior is delegated

md
+------------------+
|       Car        |
|------------------|
| + Engine engine  |----+
| + drive()        |    |
+------------------+    |
                         v
                +------------------+
                |      Engine      |
                |------------------|
                | + start()        |
                | + stop()         |
                +------------------+

C++ Example

cpp
class Engine {
public:
    void start() {
        std::cout << "Engine started\n";
    }
};

class Car {
    Engine engine;   // HAS-A relationship
public:
    void drive() {
        engine.start();
        std::cout << "Car driving\n";
    }
};

Why Composition Is Preferred

  • No base-class fragility
  • No virtual dispatch required
  • Behavior can change dynamically
  • Easier testing and reuse

Substitution vs Delegation (Key Difference)

Inheritance (Substitution)

Car can replace Vehicle

cpp
Vehicle* v = new Car();
v->move();

Composition (Delegation)

Car USES Engine

cpp
car.drive();   // delegates to engine

Composition does not promise substitutability — and that’s a feature.


Performance Perspective

Inheritance Cost

ptr -> vtable -> function

  • One extra indirection
  • Prevents inlining
  • Harder for optimizer

Composition Cost

direct call -> inlineable

  • Zero runtime overhead
  • Better cache locality
  • Better for hot paths

Composition is usually faster, Inheritance trades performance for flexibility


When Inheritance Is the Wrong Choice

  • Reusing code
  • Sharing data layout
  • Avoiding duplication
  • Convenience

cpp
class Logger {};
class FileLogger : public Logger {}; // likely wrong

Logger is a service, not a base type.


Composition + Interfaces (Best of Both Worlds)

Pattern: Interface + Composition

md
+------------------+
|   Drawable (IF)  |
|------------------|
| + draw() = 0     |
+---------^--------+
		  |
+------------------+
|      Sprite      |
|------------------|
| + Renderer r     |
+------------------+

cpp
class Drawable {
public:
    virtual void draw() = 0;
    virtual ~Drawable() = default;
};

class Renderer {
public:
    void render() {
        std::cout << "Rendering\n";
    }
};

class Sprite : public Drawable {
    Renderer renderer;
public:
    void draw() override {
        renderer.render();
    }
};

  • Polymorphism where needed
  • Composition for implementation
  • Minimal inheritance depth

Decision Table

md
Need runtime polymorphism?      → Inheritance
Need code reuse only?           → Composition
Behavior varies at runtime?     → Interface + Composition
Hot performance path?           → Composition
Stable abstraction?             → Inheritance


Rule of Thumb (Memorize This)

md
Prefer composition.
Use inheritance only for polymorphism.
Never inherit for reuse.


Architectural Summary Diagram

md
+------------------+
|   Interface      |
+---------^--------+
		  |
+------------------+
|   Concrete Obj   |
|------------------|
|  composed parts  |
+------------------+

This is how modern C++ systems are built.