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)

class Base {
protected:
std::string _name;
int _hitPoints;
public:
Base(std::string name);
void attack(const std::string& target);
};
class Derived : public Base { // Derived inherits from Base
public:
Derived(std::string name);
void specialAbility();
};
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
  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 { };