Updated on 26 Jun, 202632 mins read 23 views

Introduction

Imagine someone asks you to build software for a zoo.

You identity the following animals:

  • Dog
  • Cat
  • Elephant
  • Lion
  • Tiger
  • Horse

You begin writing classes.

class Dog
{
public:
    void eat() {}
    void sleep() {}
    void breathe() {}
};

class Cat
{
public:
    void eat() {}
    void sleep() {}
    void breathe() {}
};

class Lion
{
public:
    void eat() {}
    void sleep() {}
    void breathe() {}
};

After writing only three classes, something feels wrong.

The code is almost identical.

If you continue, you will duplicate the same function dozens of times.

Now consider another example:

Vehicles.

  • Car
  • Bus
  • Truck
  • Motorcyle

Again:

class Car
{
public:
    void startEngine() {}
    void stopEngine() {}
};

class Bus
{
public:
    void startEngine() {}
    void stopEngine() {}
};

class Truck
{
public:
    void startEngine() {}
    void stopEngine() {}
};

The same pattern appears.

We have many objects that share common behavior.

This problem existed long before C++.

Early software systems often duplicated logic across multiple modules, making maintenance difficult and error-prone.

Inheritance was introduced to solve this problem.

Historical Context

To understand inheritance, we must first understand the programming world before object-oriented programming.

Procedural Programming Era

Languages like C focused on functions.

Data and behavior were separate.

For example:

struct Employee
{
    char name[50];
    int age;
};

void printEmployee(Employee* e);
void calculateSalary(Employee* e);
void promote(Employee* e);

As systems grew, many structures became similar.

For example:

Employee
Manager
Developer
Tester
Intern

Each structure contained common information:

Name
Age
ID

Programmers repeatedly copied fields and functions.

This led to:

  • Massive code duplication.
  • Inconsistent behavior.
  • Difficult maintenance.

The question became:

“Why are we rewriting the same logic over and over?”

Object-oriented programming answered this with inheritance.

What Is Inheritance?

Definition

Inheritance is a mechanism by which one class acquires the properties and behaviors of another class.

The existing class is called the base class (or parent class).

The new class is called the derived class (or child class).

Textually:

           Animal
              ▲
              │
     ┌────────┴────────┐
     │                 │
    Dog               Cat

Instead of writing common functionality multiple times.

class Animal
{
public:
    void eat()
    {
        std::cout << "Eating\n";
    }

    void sleep()
    {
        std::cout << "Sleeping\n";
    }
};

Dog inherits it:

class Dog : public Animal
{
};

Cat inherits it:

class Cat : public Animal
{
};

Usage:

Dog dog;
dog.eat();
dog.sleep();

Cat cat;
cat.eat();

Understanding Generalization

Many beginners think inheritance and generalization are identical.

They are related, but not the same.

This distinction is critical.

Generalization

Generalization is a design activity.

It happens during analysis.

We ask:

“What characteristics are common among these objects”

Consider:

Dog
Cat
Horse
Tiger

All of them:

  • Eat
  • Sleep
  • Breathe
  • Reproduce

We generalize them into:

Animal

Diagram:

Dog
Cat
Horse
Tiger

↓

Animal

Generalization is a way of discovering abstractions.

Inheritance

Inheritance is the programming mechanism used to implement that abstraction.

Generalization exists in the problem domain.

Inheritance exists in the programming language.

A useful mental model is:

Generalization
        ↓
Design Decision
        ↓
Inheritance
        ↓
Implementation

The “Is-A” Relationship

Inheritance represents an Is-A relationship.

Ask yourself:

“Is this object a specialized version of another object?”

Examples:

Dog IS-A Animal

Cat IS-A Animal

Bus IS-A Vehicle

Square IS-A Shape

CheckingAccount IS-A BankAccount

These are valid inheritance relationships.

Now consider:

Engine IS-A Car

No.

An engine is part of a car.

That's not inheritance.

It's composition.

Similarly:

Student IS-A University

Wrong.

A student belongs to a university.

Not inheritance.

Another example:

Keyboard IS-A Computer

Wrong.

The keyboard is part of a computer.

Always ask the Is-A question.

If the answer is "No,", inheritance is probably incorrect.

Mental Model: Specialization, Not Duplication

Many people learn inheritance as:

“A way to reuse code.”

While this is true, it is not the most important reason to use inheritance.

The deeper purpose is specialization.

Think of inheritance as answering the question:

“How is this concept a more specialized version of another concept?”

Code reuse is a consequence – not the primary goal.

If your only reason for inheritance is “to avoid duplicate code”, you are likely to create brittle class hierarchies.

Professional designer first ask:

  1. Is there a true Is-A relationship?
  2. Does the derived class preserve the meaning of the base class?
  3. Will this hierarchy remain valid as the software evolves?

If the answer to any of these is “no”, inheritance is probably the wrong tool.

Basic Syntax of Inheritance

The general syntax is:

class DerivedClass : access_specifier BaseClass
{
    // Additional members
};

Example:

class Animal
{
public:
    void eat()
    {
        std::cout << "Eating...\n";
    }
};

class Dog : public Animal
{
};

Usage:

Dog dog;
dog.eat();

Output:

Eating...

Notice something interesting.

We never wrote an eat() function inside Dog.

Yet it works.

Why?

Because Dog inherits it from Animal.

Visualizing the Relationship

Although Dog contains no explicit members:

class Dog : public Animal
{
};

You can mentally think of it like this:

Dog

-----------------------

Inherited:

eat()

-----------------------

Or, if Animal also had data:

class Animal
{
protected:
    std::string name;
    int age;

public:
    void eat();
    void sleep();
};

Then Dog conceptually becomes:

Dog

--------------------------------

Inherited Data

name

age

--------------------------------

Inherited Behavior

eat()

sleep()

--------------------------------

The derived object contains the base object as part of its memory layout.

Constructors and Inheritance

One of the biggest misconceptions is:

“Constructors are inherited.”

They are not.

Consider:

class Animal
{
public:
    Animal()
    {
        std::cout << "Animal created\n";
    }
};

class Dog : public Animal
{
};

Usage:

Dog dog;

Output:

Animal created

Did Dog inherit the constructor?

No.

What happened?

Before constructing the Dog part, C++ first constructs the Animal part.

Construction order:

Create Dog

  ↓

Construct Animal

  ↓

Construct Dog

Think of building a house.

You don't build the roof first.

You build the foundation.

The base class is the foundation.

Constructor Chaining

Suppose:

class Animal
{
public:
    Animal()
    {
        std::cout << "Animal\n";
    }
};

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

Execution:

Dog dog;

Output:

Animal
Dog

Always remember.

Base constructors execute before derived constructors.

Passing Parameters to the Base Class

Suppose the base class needs information.

class Animal
{
protected:
    std::string name;

public:
    Animal(std::string n)
        : name(n)
    {
    }
};

The derived class must explicitly call it.

class Dog : public Animal
{
public:
    Dog(std::string n)
        : Animal(n)
    {
    }
};

Usage:

Dog dog("Buddy");

Execution:

Animal("Buddy")

  ↓

Dog("Buddy")

The derived constructor initializes the base constructor.

Destruction Order

Construction proceeds.

Animal

 ↓

Dog

Destruction proceeds in reverse.

Dog

 ↓

Animal

Example:

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

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

Output:

Dog destroyed

Animal destroyed

Why?

Because C++ destroys objects in the opposite order in which they were created.

Access Specifiers in Inheritance

Remember:

Classes already have access specifiers.

public

protected

private

Inheritance introduces another access specifier:

class Dog : public Animal

Notice the extra public.

That is called the inheritance mode.

C++ supports three modes:

public inheritance

protected inheritance

private inheritance

Each changes the accessibility of inherited members.

Public Imheritance

class Animal
{
public:
    void eat();

protected:
    int age;

private:
    int secret;
};

class Dog : public Animal
{
};

Accessibility becomes:

AnimalDog
publicpublic
protectedprotected
privateinaccessible

Nothing changes except that private members remain inaccessible.

This module a genuine Is-A relationship.

Example:

Dog dog;

dog.eat();

Protected Inheritance

class Dog : protected Animal
{
};

Transformation:

AnimalDog
publicprotected
protectedprotected
privateinaccessible

Notice:

Everything public becomes protected.

Outside users cannot access it.

Example:

Dog dog;
dog.eat(); // Error

Inside Dog, however:

class Dog : protected Animal
{
public:
    void test()
    {
        eat();
    }
};

This works.

Private Inheritance

class Dog : private Animal
{
};

Transformation:

AnimalDog
publicprivate
protectedprivate
privateinaccessible

Everything becomes private.

Outside code cannot access inherited members.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *