- Part 1: Inheritance Basics
- Part 2: Types of Inheritance
- Part 3: Access Control in Inheritance
- Part 4: Constructor & Destructor in Inheritance
- Part 5: Function Overriding
- Part 6: Virtual Functions & Polymorphism
- Part 7: Abstract Classes & Interfaces
- Part 8: Multiple Inheritance & Diamond Problem
- Part 9: Real-World Examples
- Part 10: Interview Questions
- Part 6.1: The vptr and vtable Visual Map - Internal mechanism of polymorphism
- Real-World Example: GUI Toolkit - Complete working example
- Private Inheritance vs Final Keyword - Common confusion clarified
- How to Identify When to Use Inheritance - IS-A test and decision tree
- Understanding Object Creation in Inheritance - Memory layout explained
- Constructor/Destructor Order - From constructors-destructors topic
- Types of Inheritance - Single, Multiple, Multilevel, Hierarchical, Hybrid
- What is Inheritance?
- Why use Inheritance? (Code reuse, IS-A relationship)
- Base class and Derived class
- Access specifiers in inheritance (public, protected, private)
- File:
01_inheritance_basics.cpp
- Single Inheritance
- Multiple Inheritance
- Multilevel Inheritance
- Hierarchical Inheritance
- Hybrid Inheritance
- Diamond Problem
- Virtual Inheritance (Solution)
- File:
02_types_of_inheritance.cpp
- Public inheritance (IS-A relationship)
- Protected inheritance
- Private inheritance (HAS-A implementation)
- Access specifier transformation rules
- File:
03_access_control.cpp
- Order of constructor calls (Base β Derived)
- Order of destructor calls (Derived β Base)
- Calling base class constructors
- Virtual destructors (IMPORTANT!)
- File:
04_constructor_destructor_order.cpp
- What is function overriding?
- Overriding vs Overloading
- Virtual functions
- Runtime polymorphism
- File:
05_function_overriding.cpp
- Virtual functions
- Pure virtual functions (Abstract classes)
- Virtual table (vtable)
- Runtime polymorphism
- File:
06_virtual_functions.cpp
- Pure virtual functions
- Abstract classes (cannot instantiate)
- Interfaces in C++
- Real-world examples
- File:
07_abstract_classes.cpp
- Multiple inheritance challenges
- Diamond problem
- Virtual inheritance (solution)
- Real-world use cases
- File:
08_multiple_inheritance.cpp
- GUI Toolkit Example (UIWidget, Button, TextField)
- Demonstrates polymorphic rendering engine
- Shows extensibility through inheritance
- File:
09_real_world_example.cpp
- Diamond Problem and virtual inheritance
- Virtual destructors and memory leaks
- Static vs Dynamic dispatch (vptr/vtable)
- Private inheritance vs Composition
- Abstract classes and API contracts
- See the Deep Dive Sections and Extra Knowledge sections above
This is a classic example of using polymorphism to build an extensible system.
#include <iostream>
#include <vector>
#include <string>
// 1. The Abstract Contract (The Interface)
class UIWidget {
public:
virtual void draw() const = 0; // All widgets must be drawable
virtual ~UIWidget() = default;
};
// 2. Concrete Implementations
class Button : public UIWidget {
private:
std::string m_label;
public:
Button(const std::string& label) : m_label(label) {}
void draw() const override {
std::cout << "Drawing a Button: [" << m_label << "]" << std::endl;
}
};
class TextField : public UIWidget {
private:
std::string m_text;
public:
TextField(const std::string& text = "") : m_text(text) {}
void draw() const override {
std::cout << "Drawing a TextField: |" << m_text << "|" << std::endl;
}
};
// 3. The Rendering Engine (The System)
// This function is completely decoupled from the concrete widgets.
void render(const std::vector<UIWidget*>& widgets) {
std::cout << "\n--- SCREEN REFRESH ---" << std::endl;
for (const auto* widget : widgets) {
widget->draw(); // Dynamic dispatch happens here
}
std::cout << "----------------------" << std::endl;
}
int main() {
Button ok_button("OK");
TextField name_field("Enter name");
// The rendering engine works with a list of base class pointers.
std::vector<UIWidget*> widget_list = {&ok_button, &name_field};
render(widget_list);
return 0;
}System-Level Takeaway: The render function represents a stable, core part of a larger system. It can handle any UIWidget without ever needing to be modified. This is achieved by programming to an interface (UIWidget) rather than an implementation (Button, TextField).
class GPS {
public:
void showLocation() { }
};
class SmartPhone : private GPS { // Private inheritance
// GPS interface hidden from outside
};
class AdvancedPhone : public SmartPhone {
// β
ALLOWED! Can still inherit from SmartPhone
// β But can't access GPS members
};Purpose:
- Hides base class interface from outside world
- Implementation detail (HAS-A relationship)
- Does NOT prevent further inheritance
- Further classes can inherit, but can't access private base
class Base {
public:
void baseMethod() { }
};
class Derived final : public Base { // β final keyword
// This is the LAST class in hierarchy
};
class FurtherDerived : public Derived {
// β ERROR! Cannot inherit from final class
// Compilation error!
};Purpose:
- Completely prevents ANY inheritance
- No class can derive from a final class
- Used for: Security, performance optimization, design enforcement
| Scenario | Use |
|---|---|
| Hide implementation details (HAS-A) | Private Inheritance |
| Prefer composition over private inheritance | β Composition (better!) |
| Stop ALL inheritance completely | final keyword |
| Allow inheritance but hide base members | Private Inheritance |
| Performance critical class (no vtable) | final keyword |
Real-World Examples:
// Private Inheritance (rare, prefer composition)
class Stack : private std::vector<int> {
// vector is implementation detail
// But other classes can inherit from Stack
};
// Final keyword (prevent inheritance)
class String final {
// No one should inherit from String
// It's a complete, sealed class
};
// Java example: public final class String
// C# example: public sealed class StringInterview Tip: If asked "How to prevent inheritance };
// Dog inherits eat() from Animal // Dog IS-A Animal
### Why Use Inheritance?
1. **Code Reuse** - Don't repeat common code
2. **Logical Hierarchy** - Model real-world relationships
3. **Extensibility** - Add features without changing base
4. **Polymorphism** - Treat derived objects as base objects
### IS-A vs HAS-A Relationship
```cpp
// IS-A relationship (Inheritance)
class Car : public Vehicle { }; // Car IS-A Vehicle
// HAS-A relationship (Composition)
class Car {
Engine engine; // Car HAS-A Engine
};
Rule of thumb: Use inheritance for IS-A, composition for HAS-A
Question to ask: "Is X a type of Y?"
β
YES β Use Inheritance
- Dog IS-A Animal β
- Coffee IS-A Drink β
- Manager IS-A Employee β
- SavingsAccount IS-A BankAccount β
β NO β Use Composition (or no relationship)
- Car IS-A Engine? β (Car HAS-A Engine)
- House IS-A Door? β (House HAS-A Door)
- Student IS-A Book? β (Student HAS-A Book)
Example: Analyzing Coffee Shop Drinks
Entities: Coffee, Tea, Juice, Smoothie
Common properties:
- All have a name
- All have a price
- All have a temperature
- All can be served
Common concept: DRINK
Decision: Create "Drink" as base class β
Coffee IS-A Drink? β
Tea IS-A Drink? β
Juice IS-A Drink? β
Smoothie IS-A Drink? β
Conclusion: "Drink" should be base class
class Drink { // Base class - common behavior
protected:
string name;
double price;
int temperature;
};
class Coffee : public Drink { }; // Specific type
class Tea : public Drink { };
class Juice : public Drink { };Start: Multiple similar entities exist
β
Question: Do they share characteristics?
β
YES
β
Question: "X IS-A Y" - Natural statement?
β
βββββββ΄ββββββ
YES NO
β β
Create Use Composition
Base Class or Functions
β
Extract Design hierarchy:
common Base (general)
features β
into base Derived (specific)
Observation: Car, Bike, Truck exist
Step 1: Find commonality
- All have brand, year
- All can start(), stop()
- All move on roads
Step 2: IS-A test
- Car IS-A Vehicle? β
- Bike IS-A Vehicle? β
- Truck IS-A Vehicle? β
Step 3: Identify base
Base class: Vehicle (common concept)
Result:
class Vehicle { }; // Base
class Car : public Vehicle { };
class Bike : public Vehicle { };
Observation: Manager, Developer, Intern exist
Step 1: Find commonality
- All have name, ID, salary
- All work for company
- All have common HR processes
Step 2: IS-A test
- Manager IS-A Employee? β
- Developer IS-A Employee? β
- Intern IS-A Employee? β
Step 3: Identify base
Base class: Employee
Result:
class Employee { }; // Base
class Manager : public Employee { };
class Developer : public Employee { };
Observation: Car and Engine exist
Step 1: Relationship?
- Car uses Engine
- Car contains Engine
Step 2: IS-A test
- Car IS-A Engine? β (Makes no sense!)
Step 3: Alternative
Use composition (HAS-A):
class Car {
Engine engine; // Car HAS-A Engine
};
β Don't use inheritance if:
-
No IS-A relationship
// β WRONG class Car : public Engine { }; // Car IS-A Engine? No!
-
Just for code reuse
// β WRONG - Using inheritance just to reuse utility methods class MyClass : public UtilityFunctions { }; // β CORRECT - Use composition or helper functions class MyClass { UtilityFunctions utils; // Has utility };
-
Violates Liskov Substitution Principle
// β WRONG - Square is NOT a proper Rectangle subtype class Square : public Rectangle { }; // Problem: Setting width/height independently breaks for Square
-
Creates tight coupling
// β WRONG - Implementation inheritance for convenience class ArrayList : public Vector { }; // Too tightly coupled
β Does "X IS-A Y" make logical sense?
β Are there shared properties/behaviors?
β Do I need polymorphism (treat X as Y)?
β Is there a natural hierarchy?
β Can derived class be substituted for base class?
If 3+ checks pass β Use Inheritance β
If less than 3 β Consider Composition or other design
When designing base class:
class Base {
private:
int internalDetail; // Implementation detail, hide from everyone
protected:
int sharedData; // Derived classes need access
void helperMethod() { // Derived classes can use
// ...
}
public:
void publicInterface() { // Everyone can use
// ...
}
};Guidelines:
- private: Internal implementation, derived classes don't need
- protected: Data/methods that derived classes need to access or override
- public: Interface for all users (including derived classes)
Common pattern:
class BankAccount {
private:
double balance; // Keep private (sensitive)
protected:
// Provide controlled access for derived classes
double getBalance() const { return balance; }
void setBalance(double b) { balance = b; }
public:
void deposit(double amount); // Public interface
};Common Misconception: "When derived object is created, does it reference a separate base object?"
Reality: There is ONE single object containing both base and derived parts!
class Base {
int baseVar;
};
class Derived : public Base {
int derivedVar;
};
Derived d; // What's in memory?Visual Representation:
β WRONG: Two separate objects
βββββββββββββββ
β Base object β β baseVar
βββββββββββββββ
β reference?
β
βββββββββββββββ
β Derived obj β β derivedVar
βββββββββββββββ
β
CORRECT: One object with two parts
βββββββββββββββββββββββ
β Derived Object 'd' β β ONE object in memory
β β
β βββββββββββββββββ β
β β Base Part β β β baseVar (constructed first)
β βββββββββββββββββ β
β β
β βββββββββββββββββ β
β β Derived Part β β β derivedVar (constructed second)
β βββββββββββββββββ β
βββββββββββββββββββββββ
Step 1: Memory Allocation
- Allocate space for ENTIRE object
- Size = sizeof(Base) + sizeof(Derived's own data)
Step 2: Base Constructor Runs
- Initialize base class members
- Base part is NOW ready
Step 3: Derived Constructor Runs
- Initialize derived class members
- Derived part is NOW ready
Result: ONE complete object with both parts
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {
cout << "Animal constructor: " << this << endl;
}
};
class Dog : public Animal {
private:
string breed;
public:
Dog(string n, string b) : Animal(n), breed(b) {
cout << "Dog constructor: " << this << endl;
// Notice: SAME memory address!
}
};
Dog d("Buddy", "Golden");
// Output shows SAME address for both constructors
// Proof: It's ONE object!- β Single Object: Only ONE object exists in memory
- β Embedded Parts: Base part is embedded INSIDE derived object
- β Contiguous Memory: All parts are in sequential memory locations
- β Construction Order: Base first, then derived (within same object)
- β No References: No pointers/references between base and derived parts
- β
Size:
sizeof(Derived) >= sizeof(Base) + sizeof(Derived data)
Think of derived object as a house with multiple floors:
House (Derived Object)
βββ Ground Floor (Base part) β Built first
β βββ Foundation, structure
βββ Upper Floor (Derived part) β Built on top
βββ Additional rooms
β NOT two separate buildings!
β
ONE building with two floors!
class A { int a; };
class B { int b; };
class C : public A, public B { int c; };
C obj; // Memory layout:βββββββββββββββββββββββββββββββ
β Object 'obj' (type C) β β Still ONE object!
β β
β βββββββββββββββββββββββ β
β β A Part β β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β B Part β β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β C Part β β
β βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββ
Q: When a derived class object is created, is there a separate base class object?
A: No. When a derived object is created:
- Only ONE object exists in memory
- It contains the base class part embedded within it
- The base constructor runs first to initialize the base part
- Then the derived constructor runs to initialize the derived part
- All parts are in contiguous memory (not separate objects)
- There are no references or pointers between parts - they're physically part of the same object
Think of it as a single object with layers, not separate connected objects.
Q1: What is inheritance?
Inheritance is an OOP mechanism where a derived class acquires properties and methods from a base class, enabling code reuse and establishing IS-A relationships.
Q2: What are the types of inheritance in C++?
Single, Multiple, Multilevel, Hierarchical, and Hybrid inheritance. C++ supports all types, including multiple inheritance (unlike Java).
Q3: What is the difference between public, protected, and private inheritance?
- Public: Base public members remain public in derived (IS-A relationship)
- Protected: Base public members become protected in derived
- Private: Base public/protected members become private in derived (HAS-A implementation)
Q4: Why should base class destructor be virtual?
When deleting a derived object through a base pointer, without virtual destructor, only the base destructor is called β memory leak. Virtual destructor ensures proper cleanup of derived class resources.
Q5: What is the diamond problem?
In multiple inheritance, if two base classes inherit from a common grandparent, the derived class has two copies of grandparent members. Solution: Virtual inheritance.
Q6: What is function overriding?
When a derived class provides a specific implementation of a function already defined in the base class. Requires same function signature. Use
virtualfor runtime polymorphism.
Q7: What's the difference between overloading and overriding?
- Overloading: Same function name, different parameters, compile-time (same class)
- Overriding: Same function signature, different implementation, runtime (inheritance)
Q8: What is a pure virtual function?
A virtual function with
= 0that has no implementation in base class. Makes the class abstract (cannot be instantiated). Derived classes must override it.
Q9: When to use inheritance vs composition?
Use inheritance for IS-A relationships (natural hierarchy). Use composition for HAS-A relationships (building complex objects from simpler ones). Prefer composition over inheritance when in doubt.
Q10: What is virtual inheritance?
A technique to solve the diamond problem in multiple inheritance. Uses
virtualkeyword in inheritance declaration to ensure only one copy of common base class.
- β Use public inheritance for IS-A relationships
- β Make base class destructor virtual (if using polymorphism)
- β Prefer composition over inheritance (when relationship isn't clear)
- β
Use
overridekeyword (C++11) for clarity - β Keep inheritance hierarchies shallow (avoid deep nesting)
- β Use abstract classes for interfaces (pure virtual functions)
- β Avoid multiple inheritance (unless necessary)
- β Call base constructor explicitly (in derived class initialization list)
We'll explore:
- How inheritance enables polymorphism
- Virtual function tables (vtable)
- Abstract classes and interfaces
- Real-world design patterns using inheritance
- Common pitfalls and how to avoid them
Ready to dive in? Let's start with Part 1: Inheritance Basics! π
This is the internal mechanism that makes runtime polymorphism possible. Once you understand this, you'll never forget how virtual functions work.
-
vtable(Virtual Table):- What: A static array of function pointers.
- Who: One
vtableexists per class that has at least one virtual function. - When: Created at compile time.
- Where: Stored in a read-only segment of memory. All objects of the same class share this single
vtable.
-
vptr(Virtual Pointer):- What: A hidden pointer. It's a member variable secretly added by the compiler.
- Who: One
vptrexists per object of a class with virtual functions. - When: The
vptris initialized during object construction (in the constructor). - Job: To point to the correct
vtablefor that object's class.
Let's use these classes:
class Shape {
public:
virtual void draw();
virtual void rotate();
// vptr is added here by the compiler
};
class Circle : public Shape {
public:
void draw() override; // Overrides Shape::draw
// rotate() is inherited from Shape
};At Compile Time: The compiler builds the vtables.
Shape's vtable:
[0]->&Shape::draw()[1]->&Shape::rotate()
Circle's vtable:
[0]->&Circle::draw()(Overridden)[1]->&Shape::rotate()(Inherited)
At Runtime: When you create an object, it gets its vptr.
Shape* s = new Circle();This is what happens in memory:
// In the HEAP
Circle Object (at address 0x1000)
- vptr ---------------------> // In READ-ONLY MEMORY
- (other data members...) // Circle's vtable
// [0]: &Circle::draw()
// [1]: &Shape::rotate()
The object's vptr is set to point to the vtable of its actual type (Circle).
When the code s->draw(); is executed:
-
Follow the
vptr: The program first looks inside the objectspoints to. It finds the hiddenvptr.[Circle Object] -> vptr -
Go to the
vtable: It follows thevptrto theCircle'svtable.vptr -> [Circle's vtable] -
Look up the Function: The compiler knows
draw()is the first virtual function (at index[0]). It looks up the address atvtable[0].[Circle's vtable] -> [0] -> &Circle::draw() -
Call the Function: The program calls the function at that address. In this case, it's
Circle::draw().
This is Dynamic Dispatch. The decision of which draw() to call is made at RUNTIME, based on the actual type of the object, not the type of the pointer.
If you call a non-virtual function, like s->regularFunc();:
- Check Pointer Type: The compiler sees that
sis aShape*. - Direct Call: It generates a direct, hard-coded call to
Shape::regularFunc(). - No
vptr/vtableis ever used. This is Static Dispatch.
// OBJECTS (in RAM/Heap) // VTABLES (in Read-Only Memory)
Circle obj1:
vptr ----------------------------> Circle's vtable:
- &Circle::draw()
Circle obj2: - &Shape::rotate()
vptr --------------------|
|
Rectangle obj1: |
vptr --------------------|------> Rectangle's vtable:
- &Rectangle::draw()
- &Rectangle::rotate()
// Key Takeaways:
// 1. Each OBJECT has its OWN vptr.
// 2. All objects of the SAME CLASS share ONE vtable.
// 3. The call `obj->draw()` means:
// "Follow my vptr, find the draw() entry, and call that function."
This map shows that no matter how many Circle objects you create, they all share one vtable, but each has its own vptr to
2D0A
find it. This is the elegant and efficient solution C++ uses for runtime polymorphism.