09

Module 9: Inheritance and Polymorphism

Chapter 9 • Intermediate

65 min

Inheritance and Polymorphism

Inheritance and polymorphism are core OOP concepts that enable code reuse and flexible design. They're essential for building scalable, maintainable C++ applications.

What is Inheritance?

Inheritance allows a class (derived/child class) to inherit properties and behaviors from another class (base/parent class). This promotes code reuse and establishes an "is-a" relationship.

Key Benefits:

  • Code Reuse: Don't repeat code from base class
  • Hierarchical Organization: Model real-world relationships
  • Extensibility: Add new features without modifying base class
  • Polymorphism: Use derived classes through base class pointers

Inheritance Syntax

Basic Structure:

cpp.js
class BaseClass {
    // Base class members
};

class DerivedClass : access-specifier BaseClass {
    // Derived class members
};

Access Specifiers:

  • public: Public and protected members remain accessible
  • protected: Public members become protected
  • private: Public and protected members become private

Most Common: Use public inheritance.

Simple Inheritance Example

cpp.js
class Animal {
protected:
    string name;
    
public:
    Animal(string n) : name(n) {}
    
    void eat() {
        cout << name << " is eating" << endl;
    }
    
    void sleep() {
        cout << name << " is sleeping" << endl;
    }
};

class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {}
    
    void bark() {
        cout << name << " is barking" << endl;
    }
};

int main() {
    Dog dog("Buddy");
    dog.eat();    // Inherited from Animal
    dog.sleep();  // Inherited from Animal
    dog.bark();   // Dog's own method
    return 0;
}

Access Levels in Inheritance

Base ClassInheritance TypeDerived Class Access
publicpublicpublic
protectedpublicprotected
privatepublicNot accessible
publicprotectedprotected
protectedprotectedprotected
privateprotectedNot accessible

Constructor and Destructor in Inheritance

Order of Execution:

  1. Base class constructor
  2. Derived class constructor
  3. Derived class destructor
  4. Base class destructor

Example:

cpp.js
class Base {
public:
    Base() { cout << "Base constructor" << endl; }
    ~Base() { cout << "Base destructor" << endl; }
};

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

Types of Inheritance

1. Single Inheritance

One base class, one derived class:

cpp.js
class A { };
class B : public A { };

2. Multiple Inheritance

Derived class inherits from multiple base classes:

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

Note: Can lead to ambiguity issues (diamond problem).

3. Multilevel Inheritance

Chain of inheritance:

cpp.js
class A { };
class B : public A { };
class C : public B { };

4. Hierarchical Inheritance

Multiple derived classes from one base:

cpp.js
class A { };
class B : public A { };
class C : public A { };

Function Overriding

Derived class redefines base class function:

cpp.js
class Animal {
public:
    void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() {  // Override
        cout << "Dog barks" << endl;
    }
};

Note: Without virtual functions, this is function hiding, not true polymorphism.

Virtual Functions

Enable runtime polymorphism (late binding):

cpp.js
class Animal {
public:
    virtual void makeSound() {  // Virtual function
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {  // Override virtual function
        cout << "Dog barks" << endl;
    }
};

Key Points:

  • virtual keyword enables polymorphism
  • override keyword (C++11) ensures correct overriding
  • Function called based on object type, not pointer type

Polymorphism Example

cpp.js
Animal* animal1 = new Dog("Buddy");
animal1->makeSound();  // Calls Dog::makeSound() - polymorphism!

Pure Virtual Functions

Functions with no implementation in base class:

cpp.js
class Animal {
public:
    virtual void makeSound() = 0;  // Pure virtual
};

Effects:

  • Makes class abstract (cannot instantiate)
  • Derived classes MUST implement it
  • Creates interface contract

Abstract Classes

Classes with at least one pure virtual function:

cpp.js
class Shape {
public:
    virtual double getArea() = 0;  // Pure virtual
    virtual double getPerimeter() = 0;  // Pure virtual
};

// Cannot do: Shape s;  // Error: abstract class

Virtual Destructors

Critical Rule: Always make base class destructor virtual!

cpp.js
class Base {
public:
    virtual ~Base() {  // Virtual destructor
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

Why? Ensures proper cleanup when deleting through base pointer.

Virtual Function Table (vtable)

How polymorphism works internally:

  • Each class with virtual functions has a vtable
  • Contains pointers to virtual functions
  • Object stores pointer to vtable
  • Runtime lookup determines which function to call

Function Hiding vs Overriding

Function Hiding (without virtual):

cpp.js
Base* ptr = new Derived();
ptr->function();  // Calls Base::function()

Function Overriding (with virtual):

cpp.js
Base* ptr = new Derived();
ptr->function();  // Calls Derived::function()

Multiple Inheritance Issues

Diamond Problem

cpp.js
class A { };
class B : public A { };
class C : public A { };
class D : public B, public C { };  // Two copies of A!

Solution: Virtual Inheritance

cpp.js
class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };  // One copy of A

Best Practices

  1. Use public inheritance for "is-a" relationships
  2. Make destructors virtual in base classes
  3. Use override keyword (C++11) for clarity
  4. Prefer composition over inheritance when appropriate
  5. Use abstract classes to define interfaces
  6. Avoid deep inheritance hierarchies (keep it shallow)
  7. Use virtual inheritance to solve diamond problem
  8. Document inheritance relationships clearly

Common Mistakes

  • ❌ Forgetting virtual destructor in base class
  • ❌ Using inheritance for "has-a" relationships (use composition)
  • ❌ Deep inheritance hierarchies (hard to maintain)
  • ❌ Not understanding virtual function overhead
  • ❌ Diamond problem without virtual inheritance
  • ❌ Hiding instead of overriding (missing virtual)

Next Module

In Module 10, we'll learn about STL (Standard Template Library) - powerful containers, algorithms, and iterators that make C++ programming efficient!

Hands-on Examples

Basic Inheritance

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

class Animal {
protected:
    string name;
    int age;
    
public:
    Animal(string n, int a) : name(n), age(a) {
        cout << "Animal constructor" << endl;
    }
    
    void eat() {
        cout << name << " is eating" << endl;
    }
    
    void sleep() {
        cout << name << " is sleeping" << endl;
    }
    
    void display() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

class Dog : public Animal {
private:
    string breed;
    
public:
    Dog(string n, int a, string b) : Animal(n, a), breed(b) {
        cout << "Dog constructor" << endl;
    }
    
    void bark() {
        cout << name << " is barking" << endl;
    }
    
    void display() {
        Animal::display();  // Call base class method
        cout << "Breed: " << breed << endl;
    }
};

int main() {
    Dog dog("Buddy", 3, "Golden Retriever");
    
    dog.eat();     // Inherited from Animal
    dog.sleep();   // Inherited from Animal
    dog.bark();    // Dog's own method
    dog.display(); // Dog's overridden method
    
    return 0;
}

Inheritance allows Dog to inherit members from Animal. Protected members are accessible in derived class. Derived class can add new members and override base class methods. Base constructor called before derived constructor.