Skip to content

Module 03: Inheritance

Download Official Subject PDF

Key Concepts:

  • Base and derived classes
  • Access specifiers with inheritance
  • Constructor/destructor chaining
  • Member access in inheritance
  • Multiple inheritance (Diamond Problem)

Why Inheritance? (The Problem: Code Duplication)

Section titled “Why Inheritance? (The Problem: Code Duplication)”

Imagine you’re writing a game with different robot types:

// Without inheritance - lots of duplicated code!
class ClapTrap {
std::string _name;
int _hitPoints;
int _energyPoints;
void attack(std::string target);
void takeDamage(int amount);
void beRepaired(int amount);
};
class ScavTrap {
std::string _name; // Duplicated!
int _hitPoints; // Duplicated!
int _energyPoints; // Duplicated!
void attack(std::string target); // Similar logic!
void takeDamage(int amount); // Duplicated!
void beRepaired(int amount); // Duplicated!
void guardGate(); // Only this is unique
};
class FragTrap {
std::string _name; // Duplicated AGAIN!
// ... same duplication problem
};

This is painful:

  • Same code written multiple times
  • Bug fixes must be applied in multiple places
  • Adding features means editing many classes

Inheritance solves this: Extract common code into a base class, then derive specialized classes that add unique features:

class ClapTrap { /* common code */ };
class ScavTrap : public ClapTrap { void guardGate(); }; // Adds guardGate
class FragTrap : public ClapTrap { void highFivesGuys(); }; // Adds highFives

Understanding Inheritance Syntax (For Beginners)

Section titled “Understanding Inheritance Syntax (For Beginners)”

This module introduces class inheritance. Here are the new operators and syntax you’ll encounter:

class Derived : public Base { }; // Derived inherits from Base
  • : (colon) introduces the inheritance list
  • It says “this class IS-A kind of that class”
  • public means base class public members stay public in derived class
  • Read it as: “Derived inherits publicly from Base”

The virtual Keyword (Inheritance Modifier)

Section titled “The virtual Keyword (Inheritance Modifier)”
class Middle : virtual public Base { }; // Virtual inheritance
  • virtual in inheritance solves the “diamond problem”
  • Without virtual, a class inheriting from two classes that share a base gets TWO copies of the base
  • With virtual, only ONE copy of the base class exists
  • Use this when multiple inheritance creates a diamond shape in the class hierarchy
ClapTrap::attack(target); // Call base class version
Base::operator=(other); // Call base assignment operator
  • ClassName:: lets you explicitly call a base class method
  • This is necessary when you’ve overridden (redefined) a method in the derived class
  • Use it to access the base class implementation from the derived class
using ScavTrap::attack; // Bring ScavTrap's attack into current scope
using Base::print; // Bring ALL Base::print overloads into scope
  • using imports names from another scope into the current scope
  • In inheritance, it brings base class methods into the derived class scope
  • Useful when you override some overloads but want to keep others accessible
  • Without using Base::print, if you override print() (no parameters), the print(int) overload becomes hidden
class Base {
protected: // Accessible in this class AND derived classes
int _hitPoints;
public: // Accessible everywhere
void attack();
};
  • protected is between private and public
  • Private: Only the class itself can access
  • Protected: The class AND its derived classes can access
  • Public: Everyone can access
  • Use protected for members that derived classes need to use but outsiders shouldn’t access

Constructor Chaining with Initialization List

Section titled “Constructor Chaining with Initialization List”
ScavTrap(std::string name) : ClapTrap(name) {
// Base constructor called first, then this runs
}
  • The initialization list (: before the {) chains to the base constructor
  • ClapTrap(name) calls the base class constructor with the name parameter
  • Base class constructor runs BEFORE derived class constructor body
  • You MUST call the base constructor if the base has no default constructor

Why base runs first? A derived object contains the base object “inside” it. The base part must be fully constructed before the derived part can use it. Think of building a house: you need the foundation (base) before you can add the upper floors (derived).

ScavTrap(const ScavTrap& other) : ClapTrap(other) {
// other is a ScavTrap, which IS-A ClapTrap
}
  • ClapTrap(other) calls the base copy constructor
  • Even though other is a ScavTrap, it can be passed as ClapTrap& because inheritance IS-A relationship
  • This copies the base class members; you copy derived class members in the body

class Base {
protected:
std::string _name;
int _hitPoints;
public:
Base(std::string name);
void attack(const std::string& target);
};
class Derived : public ClapTrap { // Derived inherits from ClapTrap
public:
Derived(std::string name);
void specialAbility();
};
┌─────────────────────────────────────────────────────────────────┐
│ Inheritance = "IS-A" Relationship │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Visual Model: │
│ ┌───────────────┐ │
│ │ ClapTrap │ ←── Base (parent) class │
│ │ ───────── │ Defines common attributes │
│ │ _name │ and behaviors │
│ │ _hitPoints │ │
│ │ attack() │ │
│ └───────┬───────┘ │
│ │ │
│ │ "IS-A" │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────┐ │
│ │ScavTrap│ │FragTrap│ ←── Derived (child) classes │
│ │ IS-A │ │ IS-A │ Inherit from ClapTrap │
│ │ClapTrap│ │ClapTrap│ + add specializations │
│ │────────│ │────────│ │
│ │guardG- │ │highF- │ │
│ │ate() │ │ives() │ │
│ └────────┘ └────────┘ │
│ │
│ Memory Layout: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ClapTrap portion (inherited) │ │
│ │ ┌─────────────┐ │ │
│ │ │ _name │ │ │
│ │ │ _hitPoints │ │ │
│ │ │ _energy │ ←── Base class data │ │
│ │ └─────────────┘ │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ ScavTrap portion (added) │ │
│ │ ┌─────────────┐ │ │
│ │ │ _gateMode │ ←── Derived class data │ │
│ │ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Read it as: "ScavTrap IS-A ClapTrap" │
│ - A ScavTrap can do everything a ClapTrap can do │
│ - Plus ScavTrap adds its own special abilities │
│ - Use inheritance for "is-a" relationships │
│ - Use composition for "has-a" relationships │
│ │
└─────────────────────────────────────────────────────────────────┘
Member TypeInherited?
Public membersYes (as public)
Protected membersYes (as protected)
Private membersNo (exist but inaccessible)
ConstructorsNo (but can be called)
DestructorsNo (but automatically called)

class Derived : public Base {
// Base public -> Derived public
// Base protected -> Derived protected
// Base private -> inaccessible
};
class Derived : protected Base {
// Base public -> Derived protected
// Base protected -> Derived protected
// Base private -> inaccessible
};
class Derived : private Base {
// Base public -> Derived private
// Base protected -> Derived private
// Base private -> inaccessible
};
In Derived Class Outside Derived
Public Inheritance:
Base public public accessible
Base protected protected not accessible
Base private - -
Protected Inheritance:
Base public protected not accessible
Base protected protected not accessible
Base private - -
Private Inheritance:
Base public private not accessible
Base protected private not accessible
Base private - -

  1. Base class constructor runs FIRST
  2. Derived class constructor runs SECOND
class ClapTrap {
public:
ClapTrap(std::string name) {
std::cout << "ClapTrap constructor" << std::endl;
}
};
class ScavTrap : public ClapTrap {
public:
ScavTrap(std::string name) : ClapTrap(name) {
std::cout << "ScavTrap constructor" << std::endl;
}
};
// Creating ScavTrap prints:
// ClapTrap constructor
// ScavTrap constructor
┌─────────────────────────────────────────────────────────────────┐
│ Constructor Execution Order in Inheritance │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Construction Order (Top-Down): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 1. BASE │ ◄── ClapTrap constructor runs FIRST │ │
│ │ │ CONSTRUCTOR │ Initialize inherited members │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 2. MEMBER │ ◄── Member variables constructors │ │
│ │ │ CONSTRUCTORS│ (if any members are objects) │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 3. DERIVED │ ◄── ScavTrap constructor body runs │ │
│ │ │ CONSTRUCTOR │ Initialize own members │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ [OBJECT READY] │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Code Example Flow: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ScavTrap s("Scavvy"); │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ ScavTrap::ScavTrap(string name) │ │ │
│ │ │ : ClapTrap(name) ◄── calls base │ │ │
│ │ │ { │ │ │
│ │ │ // Base already constructed │ │ │
│ │ │ _hitPoints = 100; ◄── modify │ │ │
│ │ │ } │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ ClapTrap::ClapTrap(string name) { │ │ │
│ │ │ _name = name; │ │ │
│ │ │ _hitPoints = 10; │ │ │
│ │ │ } │ │ │
│ │ │ ↓ completes first │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ returns to derived │ │
│ │ [Derived constructor body executes] │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Destruction Order (Bottom-Up - REVERSE): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 1. DERIVED │ ◄── ScavTrap destructor runs FIRST │ │
│ │ │ DESTRUCTOR │ Clean up own resources │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 2. MEMBER │ ◄── Member destructors │ │
│ │ │ DESTRUCTORS │ │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 3. BASE │ ◄── ClapTrap destructor runs LAST │ │
│ │ │ DESTRUCTOR │ Clean up inherited resources │ │
│ │ └─────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Remember: Construction is TOP-DOWN (base → derived) │
│ Destruction is BOTTOM-UP (derived → base) │
│ │
└─────────────────────────────────────────────────────────────────┘
  1. Derived class destructor runs FIRST
  2. Base class destructor runs SECOND
class ClapTrap {
public:
~ClapTrap() {
std::cout << "ClapTrap destructor" << std::endl;
}
};
class ScavTrap : public ClapTrap {
public:
~ScavTrap() {
std::cout << "ScavTrap destructor" << std::endl;
}
};
// Destroying ScavTrap prints:
// ScavTrap destructor
// ClapTrap destructor
class ScavTrap : public ClapTrap {
public:
// MUST call base constructor in initialization list
ScavTrap(std::string name) : ClapTrap(name) {
// Base is already constructed here
_hitPoints = 100; // Override base values
_energyPoints = 50;
_attackDamage = 20;
}
};

The copy constructor must explicitly call the base class copy constructor:

class ScavTrap : public ClapTrap {
public:
// Copy constructor - call base copy constructor
ScavTrap(const ScavTrap& other) : ClapTrap(other) {
// other is a ScavTrap, which IS-A ClapTrap
// Base class copy constructor handles inherited members
std::cout << "ScavTrap copy constructor" << std::endl;
}
};

The assignment operator should call the base class assignment operator:

class ScavTrap : public ClapTrap {
public:
ScavTrap& operator=(const ScavTrap& other) {
if (this != &other) {
// Call base class assignment operator
ClapTrap::operator=(other);
// Assign derived class members (if any)
// _derivedMember = other._derivedMember;
}
return *this;
}
};

Complete Orthodox Canonical Form for Derived Class

Section titled “Complete Orthodox Canonical Form for Derived Class”
class ScavTrap : public ClapTrap {
public:
// Default constructor
ScavTrap() : ClapTrap() {
_hitPoints = 100;
_energyPoints = 50;
_attackDamage = 20;
}
// Parameterized constructor
ScavTrap(std::string name) : ClapTrap(name) {
_hitPoints = 100;
_energyPoints = 50;
_attackDamage = 20;
}
// Copy constructor
ScavTrap(const ScavTrap& other) : ClapTrap(other) {
// Derived members copied here if any
}
// Assignment operator
ScavTrap& operator=(const ScavTrap& other) {
if (this != &other) {
ClapTrap::operator=(other);
}
return *this;
}
// Destructor
~ScavTrap() {
// Derived cleanup (base destructor called automatically)
}
};

class ClapTrap {
public:
void attack(const std::string& target) {
std::cout << "ClapTrap " << _name << " attacks " << target << std::endl;
}
};
class ScavTrap : public ClapTrap {
public:
void attack(const std::string& target) {
std::cout << "ScavTrap " << _name << " attacks " << target << std::endl;
}
};
ClapTrap clap("Clappy");
ScavTrap scav("Scavvy");
clap.attack("enemy"); // "ClapTrap Clappy attacks enemy"
scav.attack("enemy"); // "ScavTrap Scavvy attacks enemy"
class ScavTrap : public ClapTrap {
public:
void attack(const std::string& target) {
// Call base class version
ClapTrap::attack(target);
// Add extra behavior
std::cout << "...with extra ScavTrap power!" << std::endl;
}
};

When a derived class declares a member with the same name as a base class member, it shadows (hides) the base class member:

class Base {
public:
void print() { std::cout << "Base" << std::endl; }
void print(int x) { std::cout << "Base: " << x << std::endl; }
};
class Derived : public Base {
public:
void print() { std::cout << "Derived" << std::endl; }
// print(int) is now HIDDEN!
};
Derived d;
d.print(); // OK: "Derived"
d.print(42); // ERROR: print(int) is shadowed!
d.Base::print(42); // OK: explicit scope resolution

Bring base class members into derived class scope:

class DiamondTrap : public ScavTrap, public FragTrap {
public:
// Bring ScavTrap's attack into DiamondTrap's scope
using ScavTrap::attack;
// Now DiamondTrap::attack() calls ScavTrap::attack()
};
// Also useful to un-hide overloaded functions:
class Derived : public Base {
public:
using Base::print; // Bring ALL Base::print overloads
void print() { std::cout << "Derived" << std::endl; }
};
Derived d;
d.print(); // OK: "Derived"
d.print(42); // OK: Base::print(int) is now visible

class ClapTrap {
private:
std::string _name; // Only ClapTrap can access
protected:
int _hitPoints; // ClapTrap AND derived classes can access
public:
void display(); // Everyone can access
};
class ScavTrap : public ClapTrap {
public:
void specialAttack() {
// _name = "X"; // ERROR: private in ClapTrap
_hitPoints -= 10; // OK: protected is accessible
}
};
class Base {
private:
int _private; // Only Base methods
protected:
int _protected; // Base + Derived methods
public:
int _public; // Everyone
};

class ClapTrap {
protected:
std::string _name;
int _hitPoints; // 10
int _energyPoints; // 10
int _attackDamage; // 0
public:
ClapTrap(std::string name);
ClapTrap(const ClapTrap& other);
ClapTrap& operator=(const ClapTrap& other);
~ClapTrap();
void attack(const std::string& target);
void takeDamage(unsigned int amount);
void beRepaired(unsigned int amount);
};
class ScavTrap : public ClapTrap {
public:
ScavTrap(std::string name);
ScavTrap(const ScavTrap& other);
ScavTrap& operator=(const ScavTrap& other);
~ScavTrap();
void attack(const std::string& target); // Override
void guardGate(); // New ability
};
// Constructor must initialize base with different values:
// _hitPoints = 100, _energyPoints = 50, _attackDamage = 20
class FragTrap : public ClapTrap {
public:
FragTrap(std::string name);
FragTrap(const FragTrap& other);
FragTrap& operator=(const FragTrap& other);
~FragTrap();
void highFivesGuys(); // New ability
};
// _hitPoints = 100, _energyPoints = 100, _attackDamage = 30

7. Multiple Inheritance and the Diamond Problem (ex03)

Section titled “7. Multiple Inheritance and the Diamond Problem (ex03)”
ClapTrap
/ \
ScavTrap FragTrap
\ /
DiamondTrap

Without virtual inheritance:

  • DiamondTrap has TWO copies of ClapTrap
  • Ambiguity: which ClapTrap’s _name?
class ClapTrap {
// ...
};
class ScavTrap : virtual public ClapTrap {
// ^^^^^^^ KEY WORD
};
class FragTrap : virtual public ClapTrap {
// ^^^^^^^ KEY WORD
};
class DiamondTrap : public ScavTrap, public FragTrap {
// Now only ONE ClapTrap subobject
};
class DiamondTrap : public ScavTrap, public FragTrap {
private:
std::string _name; // Same variable name as ClapTrap::_name
public:
DiamondTrap(std::string name);
~DiamondTrap();
// Uses ScavTrap's attack
using ScavTrap::attack;
void whoAmI();
};
DiamondTrap::DiamondTrap(std::string name)
: ClapTrap(name + "_clap_name"), // Initialize virtual base
ScavTrap(name),
FragTrap(name),
_name(name)
{
// Attributes from FragTrap except energy from ScavTrap
_hitPoints = FragTrap::_hitPoints; // or just 100
_energyPoints = ScavTrap::_energyPoints; // or just 50
_attackDamage = FragTrap::_attackDamage; // or just 30
}
void DiamondTrap::whoAmI() {
std::cout << "I am " << _name << std::endl;
std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;
}

With virtual inheritance, the MOST DERIVED class must initialize the virtual base:

DiamondTrap::DiamondTrap(std::string name)
: ClapTrap(name + "_clap_name"), // DiamondTrap initializes ClapTrap
ScavTrap(name), // ScavTrap's ClapTrap init is ignored
FragTrap(name), // FragTrap's ClapTrap init is ignored
_name(name)
{}

  • “Is-a” relationship: ScavTrap IS A ClapTrap
  • Code reuse: Derived classes share base class code
  • Polymorphism: Treat derived as base (Module 04)
  • “Has-a” relationship: Use composition instead
  • Just for code reuse with unrelated classes
  • When relationship doesn’t make semantic sense
  1. Forgetting to call base constructor
  2. Wrong destruction order expectations
  3. Accessing private (not protected) base members
  4. Not using virtual inheritance for diamond

// Basic inheritance
class Derived : public Base { };
// Constructor chaining
Derived(args) : Base(base_args), _member(val) { }
// Override function
void Derived::method() { Base::method(); /* extra */ }
// Virtual inheritance (diamond)
class Middle : virtual public Base { };