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
InternEach structure contained common information:
Name
Age
IDProgrammers 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 CatInstead 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
TigerAll of them:
- Eat
- Sleep
- Breathe
- Reproduce
We generalize them into:
AnimalDiagram:
Dog
Cat
Horse
Tiger
↓
AnimalGeneralization 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
↓
ImplementationThe “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 BankAccountThese are valid inheritance relationships.
Now consider:
Engine IS-A CarNo.
An engine is part of a car.
That's not inheritance.
It's composition.
Similarly:
Student IS-A UniversityWrong.
A student belongs to a university.
Not inheritance.
Another example:
Keyboard IS-A ComputerWrong.
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:
- Is there a true Is-A relationship?
- Does the derived class preserve the meaning of the base class?
- 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 createdDid 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 DogThink 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
DogAlways 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
↓
DogDestruction proceeds in reverse.
Dog
↓
AnimalExample:
class Animal
{
public:
~Animal()
{
std::cout << "Animal destroyed\n";
}
};
class Dog : public Animal
{
public:
~Dog()
{
std::cout << "Dog destroyed\n";
}
};Output:
Dog destroyed
Animal destroyedWhy?
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
privateInheritance introduces another access specifier:
class Dog : public AnimalNotice the extra public.
That is called the inheritance mode.
C++ supports three modes:
public inheritance
protected inheritance
private inheritanceEach 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:
| Animal | Dog |
|---|---|
| public | public |
| protected | protected |
| private | inaccessible |
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:
| Animal | Dog |
|---|---|
| public | protected |
| protected | protected |
| private | inaccessible |
Notice:
Everything public becomes protected.
Outside users cannot access it.
Example:
Dog dog;
dog.eat(); // ErrorInside Dog, however:
class Dog : protected Animal
{
public:
void test()
{
eat();
}
};This works.
Private Inheritance
class Dog : private Animal
{
};Transformation:
| Animal | Dog |
|---|---|
| public | private |
| protected | private |
| private | inaccessible |
Everything becomes private.
Outside code cannot access inherited members.
Member Functions in an Inheritance Hierarchy
So, far, we have inherited functions exactly as they were.
class Animal
{
public:
void eat()
{
std::cout << "Animal is eating\n";
}
};
class Dog : public Animal
{
};Usage:
Dog dog;
dog.eat();Output:
Animal is eatingThe derived class simply inherited the function.
But what happens when the derived class wants different behavior?
Why Should a Derived Class Change Behavior?
Consider animals.
Every animal makes a sound.
But not every animal makes the same sound.
Animal
↓
Dog → Bark
Cat → Meow
Cow → Moo
Lion → RoarClearly,
Animal::makeSound()cannot have one implementation that works for every animal.
Each dervied class must specialize the behavior/
This leads us to function overriding.
Function Overriding
Suppose we write
class Animal
{
public:
void makeSound()
{
std::cout << "Some generic sound\n";
}
};
class Dog : public Animal
{
public:
void makeSound()
{
std::cout << "Bark\n";
}
};Usage:
Dog dog;
dog.makeSound();Output:
BarkLooks good.
Now let's try something interesting.
Animal animal;
animal.makeSound();Output:
Some generic soundStill good.
So, what's the problem?
The problem appears when we use base-class pointers.
The Surprising Behavior
Suppose:
Dog dog;
Animal* animal = &dog;
animal.makeSound();What should happens?
Many would answer:
BarkActual output:
Some generic soundWhy?
Because the compiler only sees
Animal*The derived implementation is ignored.
This surprises almost everyone.
Static Binding
This behavior is called static binding.
The compiler decides at compile time which function to call.
Compiler
↓
Pointer Type
↓
Function SelectionSince the pointer is:
Animal*the compiler chooses
Animal::makeSound()It never looks at the real object.
Why Is This a Problem?
Imagine a Zoo application.
Dog
Cat
Lion
Elephant
HorseSuppose we store them as:
Animal*std::vector<Animal*> animals;Now we write:
for (Animal* animal : animals)
{
animal->makeSound();
}Expected output:
Bark
Meow
Roar
Trumpet
NeighActual output:
Generic Sound
Generic Sound
Generic Sound
Generic SoundCompletely useless.
Inheritance alone cannot solve this.
Dynamic Dispatch
What we actually want is:
Animal*
↓
Look at actual object
↓
Call correct functionInstead of
Animal*
↓
Call Animal functionThis mechanism is called Dynamic Dispatch.
C++ implements it using the keyword: virtual
Virtual Functions
Now modify the base class:
class Animal
{
public:
virtual void makeSound()
{
std::cout << "Generic Sound\n";
}
};Derived class
class Dog : public Animal
{
public:
void makeSound() override
{
std::cout << "Bark\n";
}
};Usage:
Dog dog;
Animal* animal = &dog;
animal->makeSound();Output:
BarkExactly what we wanted.
Why Does virtual Work?
Without virtual
Compiler
↓
Pointer Type
↓
Call FunctionWith virtual:
Compiler
↓
Object Type at Runtime
↓
Call FunctionNotice the difference. The decision is delayed until the program is actually running.
Hence the name, Runtime Polymorphism.
The override Keyword
Modern C++ introduced override
Example:
class Animal
{
public:
virtual void speak();
};
class Dog : public Animal
{
public:
void speak() override;
};Why use it?
Suppose you accidently write:
class Dog : public Animal
{
public:
void speek() override;
};Compiler:
ErrorWithout override, the compiler would silently create a new function.
This is one of the most useful safely features in modern C++.
Professional recommendation:
Always use override when overriding a virtual function.
Function Hiding
Function Hiding and Function Overriding are completely different:
Function hiding occurs when a function in a derived class has the same name as a function in the base class. The derived class function hides all base class functions with that name, even if the parameter lists are different.
Consider:
#include <iostream>
using namespace std;
class Base {
public:
void display() {
cout << "Base display()" << endl;
}
void display(int x) {
cout << "Base display(int): " << x << endl;
}
};
class Derived : public Base {
public:
void display(double x) {
cout << "Derived display(double): " << x << endl;
}
};
int main() {
Derived d;
d.display(3.14); // Calls Derived::display(double)
// d.display(); // Error: Base::display() is hidden
// d.display(10); // Error: Base::display(int) is hidden
return 0;
}Why does this happen?
When the compiler sees that Derived defines a function named display, it hides all display functions from Base, regardless of their signatures.
How to access the hidden base class functions
Use the using declaration:
#include <iostream>
using namespace std;
class Base {
public:
void display() {
cout << "Base display()" << endl;
}
void display(int x) {
cout << "Base display(int): " << x << endl;
}
};
class Derived : public Base {
public:
using Base::display; // Bring all Base::display overloads into scope
void display(double x) {
cout << "Derived display(double): " << x << endl;
}
};
int main() {
Derived d;
d.display(); // Base display()
d.display(10); // Base display(int): 10
d.display(3.14); // Derived display(double)
return 0;
}Another way
You can explicitly call the base class version.
d.Base::display();
d.Base::display(10);Calling Base-Class Implementations
Sometimes the derived class wants to extend behavior rather than replace it.
Example:
class Animal
{
public:
virtual void move()
{
std::cout << "Moving\n";
}
};Derived class:
class Dog : public Animal
{
public:
void move() override
{
Animal::move();
std::cout << "Running\n";
}
};Output:
Moving
RunningMulti-Level Inheritance
Unlike many languages,
C++ allows:
class C : public A, public B
{
};Example:
class Printer
{
};
class Scanner
{
};
class AllInOne :
public Printer,
public Scanner
{
};This seems useful.
But it introduces one of the biggest problem in C++, The Diamond Problem.
The Diamond Problem
Imagine:
Animal
/ \
Mammal Bird
\ /
FlyingBatNow suppose Animal contains age
Question: How many copies of age does FlyingBat have?
Two, One through Mammel and the other one through Bird.
Now:
Flying.agebecomes ambiguous.
Compiler Error, this is famous Diamond Problem.
Virtual Inheritance
C++ solves it using:
virtualinheritance.
class Animal
{
};
class Mammal :
virtual public Animal
{
};
class Bird :
virtual public Animal
{
};
class FlyingBat :
public Mammal,
public Bird
{
};Now there is only one Animal object.
Why Multiple Inheritance Is Rare
Although C++ supports it, large software systems use it sparingly.
Reasons:
- Increased complexity
- Ambiguous member access
- Diamond Problem
- Harder maintenance
- Difficult mental model
Composition usually produces cleaner designs.
Leave a comment
Your email address will not be published. Required fields are marked *


