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)

Inheritance is the foundation of object-oriented design. It enables code reuse and establishes relationships between types.


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] │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 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

Create a base class ClapTrap - a little robot that can:

  • attack: Deals damage, costs 1 energy point
  • takeDamage: Reduces hit points
  • beRepaired: Restores hit points, costs 1 energy point

Starting attributes:

AttributeValue
Name(constructor parameter)
Hit points10
Energy points10
Attack damage0

Step 1 - Plan for inheritance

Even though this exercise doesn’t require inheritance, the next ones do. Use protected for attributes so derived classes can access them.

Step 2 - Implement resource checking

Actions require energy and HP. If either is zero, the action fails.

Step 3 - Follow OCF

Include all four Orthodox Canonical Form members with constructor/destructor messages.

Stage 1 - Class declaration:

ClapTrap.hpp
#ifndef CLAPTRAP_HPP
#define CLAPTRAP_HPP
#include <string>
class ClapTrap {
protected: // Protected, not private - derived classes need access!
std::string _name;
int _hitPoints;
int _energyPoints;
int _attackDamage;
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);
};
#endif

Stage 2 - Constructor with messages:

ClapTrap.cpp
#include "ClapTrap.hpp"
#include <iostream>
ClapTrap::ClapTrap(std::string name)
: _name(name),
_hitPoints(10),
_energyPoints(10),
_attackDamage(0)
{
std::cout << "ClapTrap " << _name << " constructed" << std::endl;
}
ClapTrap::~ClapTrap() {
std::cout << "ClapTrap " << _name << " destructed" << std::endl;
}

Stage 3 - Actions with resource checking:

ClapTrap.cpp
void ClapTrap::attack(const std::string& target) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ClapTrap " << _name << " can't attack (no energy/HP)!" << std::endl;
return;
}
_energyPoints--;
std::cout << "ClapTrap " << _name << " attacks " << target
<< ", causing " << _attackDamage << " points of damage!" << std::endl;
}
void ClapTrap::takeDamage(unsigned int amount) {
_hitPoints -= amount;
if (_hitPoints < 0)
_hitPoints = 0;
std::cout << "ClapTrap " << _name << " takes " << amount
<< " damage! HP: " << _hitPoints << std::endl;
}
void ClapTrap::beRepaired(unsigned int amount) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ClapTrap " << _name << " can't repair (no energy/HP)!" << std::endl;
return;
}
_energyPoints--;
_hitPoints += amount;
std::cout << "ClapTrap " << _name << " repairs " << amount
<< " HP! HP: " << _hitPoints << std::endl;
}
LineCodeWhy
protected:Access specifierDerived classes can access, others can’t
if (_energyPoints <= 0)Resource checkCan’t act without energy
_energyPoints--Consume resourceActions cost energy
unsigned int amountUnsigned parameterDamage can’t be negative

1. Using private instead of protected

// WRONG - derived classes can't access!
private:
std::string _name;
// RIGHT - derived classes can access
protected:
std::string _name;

2. Forgetting resource checks

// WRONG - can attack with no energy!
void ClapTrap::attack(const std::string& target) {
_energyPoints--; // Goes negative!
// ...
}
// RIGHT - check first
void ClapTrap::attack(const std::string& target) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
// Handle failure
return;
}
_energyPoints--;
// ...
}
int main() {
ClapTrap a("Bob");
// Test basic actions
a.attack("Enemy");
a.takeDamage(5);
a.beRepaired(3);
// Test resource depletion
for (int i = 0; i < 12; i++) // Should fail after 10
a.attack("Enemy");
return 0;
}
ClapTrap.cpp
#include "ClapTrap.hpp"
#include <iostream>
ClapTrap::ClapTrap(std::string name)
: _name(name), _hitPoints(10), _energyPoints(10), _attackDamage(0)
{
std::cout << "ClapTrap " << _name << " constructed" << std::endl;
}
ClapTrap::ClapTrap(const ClapTrap& other)
: _name(other._name), _hitPoints(other._hitPoints),
_energyPoints(other._energyPoints), _attackDamage(other._attackDamage)
{
std::cout << "ClapTrap " << _name << " copy constructed" << std::endl;
}
ClapTrap& ClapTrap::operator=(const ClapTrap& other) {
std::cout << "ClapTrap assignment operator called" << std::endl;
if (this != &other) {
_name = other._name;
_hitPoints = other._hitPoints;
_energyPoints = other._energyPoints;
_attackDamage = other._attackDamage;
}
return *this;
}
ClapTrap::~ClapTrap() {
std::cout << "ClapTrap " << _name << " destructed" << std::endl;
}
void ClapTrap::attack(const std::string& target) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ClapTrap " << _name << " can't attack!" << std::endl;
return;
}
_energyPoints--;
std::cout << "ClapTrap " << _name << " attacks " << target
<< ", causing " << _attackDamage << " damage!" << std::endl;
}
void ClapTrap::takeDamage(unsigned int amount) {
_hitPoints -= amount;
if (_hitPoints < 0) _hitPoints = 0;
std::cout << "ClapTrap " << _name << " takes " << amount << " damage!" << std::endl;
}
void ClapTrap::beRepaired(unsigned int amount) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ClapTrap " << _name << " can't repair!" << std::endl;
return;
}
_energyPoints--;
_hitPoints += amount;
std::cout << "ClapTrap " << _name << " repairs " << amount << " HP!" << std::endl;
}

Create ScavTrap that inherits from ClapTrap with:

  • Different attribute values:

    AttributeValue
    Hit points100
    Energy points50
    Attack damage20
  • Custom attack message

  • New ability: guardGate()

Step 1 - Inherit from ClapTrap

Use : public ClapTrap to establish inheritance.

Step 2 - Constructor chaining

Call the base constructor first, THEN override the values.

Step 3 - Understand construction order

When creating a ScavTrap:

  1. ClapTrap constructor runs first
  2. Then ScavTrap constructor runs

Destruction is reverse:

  1. ScavTrap destructor runs first
  2. Then ClapTrap destructor runs

Stage 1 - Class declaration:

ScavTrap.hpp
#ifndef SCAVTRAP_HPP
#define SCAVTRAP_HPP
#include "ClapTrap.hpp"
class ScavTrap : public ClapTrap { // Inherits from ClapTrap
public:
ScavTrap(std::string name);
ScavTrap(const ScavTrap& other);
ScavTrap& operator=(const ScavTrap& other);
~ScavTrap();
void attack(const std::string& target); // Override ClapTrap's attack
void guardGate(); // New ability
};
#endif

Stage 2 - Constructor with base initialization:

ScavTrap.cpp
#include "ScavTrap.hpp"
#include <iostream>
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) {
// ClapTrap constructor already ran with default values
// Now we override with ScavTrap values
_hitPoints = 100;
_energyPoints = 50;
_attackDamage = 20;
std::cout << "ScavTrap " << _name << " constructed" << std::endl;
}
ScavTrap::~ScavTrap() {
std::cout << "ScavTrap " << _name << " destructed" << std::endl;
}

Stage 3 - Override attack and add new ability:

ScavTrap.cpp
void ScavTrap::attack(const std::string& target) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ScavTrap " << _name << " can't attack!" << std::endl;
return;
}
_energyPoints--;
std::cout << "ScavTrap " << _name << " attacks " << target
<< ", causing " << _attackDamage << " damage!" << std::endl;
}
void ScavTrap::guardGate() {
std::cout << "ScavTrap " << _name << " is now in Gate keeper mode" << std::endl;
}
LineCodeWhy
: public ClapTrapPublic inheritanceScavTrap IS-A ClapTrap
: ClapTrap(name)Call base constructorInitialize ClapTrap part first
_hitPoints = 100Override valueScavTrap has different stats
void attack(...)Override functionScavTrap has custom attack message

1. Not calling base constructor

// WRONG - ClapTrap not properly initialized!
ScavTrap::ScavTrap(std::string name) {
_name = name;
_hitPoints = 100;
// ...
}
// RIGHT - call base constructor first
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) {
_hitPoints = 100;
// ...
}

2. Setting values in initializer list (won’t override)

// WRONG - base constructor already set these!
ScavTrap::ScavTrap(std::string name)
: ClapTrap(name), _hitPoints(100) { // Error: can't init in derived
}
// RIGHT - override in constructor body
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) {
_hitPoints = 100; // Override after base construction
}
int main() {
std::cout << "=== Creating ScavTrap ===" << std::endl;
ScavTrap s("Scav");
s.attack("Target"); // Should show ScavTrap message
s.guardGate(); // ScavTrap-only ability
std::cout << "=== End of main ===" << std::endl;
return 0;
}
// Expected output:
// ClapTrap Scav constructed
// ScavTrap Scav constructed
// ScavTrap Scav attacks Target, causing 20 damage!
// ScavTrap Scav is now in Gate keeper mode
// ScavTrap Scav destructed
// ClapTrap Scav destructed
ScavTrap.cpp
#include "ScavTrap.hpp"
#include <iostream>
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) {
_hitPoints = 100;
_energyPoints = 50;
_attackDamage = 20;
std::cout << "ScavTrap " << _name << " constructed" << std::endl;
}
ScavTrap::ScavTrap(const ScavTrap& other) : ClapTrap(other) {
std::cout << "ScavTrap " << _name << " copy constructed" << std::endl;
}
ScavTrap& ScavTrap::operator=(const ScavTrap& other) {
ClapTrap::operator=(other);
return *this;
}
ScavTrap::~ScavTrap() {
std::cout << "ScavTrap " << _name << " destructed" << std::endl;
}
void ScavTrap::attack(const std::string& target) {
if (_energyPoints <= 0 || _hitPoints <= 0) {
std::cout << "ScavTrap " << _name << " can't attack!" << std::endl;
return;
}
_energyPoints--;
std::cout << "ScavTrap " << _name << " attacks " << target
<< ", causing " << _attackDamage << " damage!" << std::endl;
}
void ScavTrap::guardGate() {
std::cout << "ScavTrap " << _name << " is now in Gate keeper mode" << std::endl;
}

Create FragTrap that inherits from ClapTrap with:

AttributeValue
Hit points100
Energy points100
Attack damage30

New ability: highFivesGuys() - requests a high five.

Same pattern as ScavTrap - this exercise reinforces inheritance concepts.

FragTrap.hpp
#ifndef FRAGTRAP_HPP
#define FRAGTRAP_HPP
#include "ClapTrap.hpp"
class FragTrap : public ClapTrap {
public:
FragTrap(std::string name);
FragTrap(const FragTrap& other);
FragTrap& operator=(const FragTrap& other);
~FragTrap();
void highFivesGuys();
};
#endif
FragTrap.cpp
#include "FragTrap.hpp"
#include <iostream>
FragTrap::FragTrap(std::string name) : ClapTrap(name) {
_hitPoints = 100;
_energyPoints = 100;
_attackDamage = 30;
std::cout << "FragTrap " << _name << " constructed" << std::endl;
}
FragTrap::FragTrap(const FragTrap& other) : ClapTrap(other) {
std::cout << "FragTrap " << _name << " copy constructed" << std::endl;
}
FragTrap& FragTrap::operator=(const FragTrap& other) {
ClapTrap::operator=(other);
return *this;
}
FragTrap::~FragTrap() {
std::cout << "FragTrap " << _name << " destructed" << std::endl;
}
void FragTrap::highFivesGuys() {
std::cout << "FragTrap " << _name << " requests a high five!" << std::endl;
}

Same as ScavTrap - ensure you call the base constructor!

int main() {
FragTrap f("Frag");
f.attack("Enemy"); // Uses ClapTrap::attack (not overridden)
f.highFivesGuys(); // FragTrap-only ability
return 0;
}

Create DiamondTrap that inherits from BOTH ScavTrap AND FragTrap:

ClapTrap
/ \
ScavTrap FragTrap
\ /
DiamondTrap

This creates the diamond problem: without special handling, DiamondTrap would have TWO copies of ClapTrap!

Requirements:

  • Name: DiamondTrap’s own name, ClapTrap name = name + “_clap_name”
  • HP: FragTrap’s (100)
  • Energy: ScavTrap’s (50)
  • Damage: FragTrap’s (30)
  • attack(): ScavTrap’s
  • whoAmI(): Prints both names

Step 1 - Understand the diamond problem

Without virtual:

DiamondTrap has:
- ScavTrap::ClapTrap (one copy)
- FragTrap::ClapTrap (another copy!)

With virtual:

DiamondTrap has:
- ONE shared ClapTrap (the right way)

Step 2 - Add virtual to intermediate classes

Both ScavTrap and FragTrap must use virtual public ClapTrap.

Step 3 - Initialize virtual base in most-derived class

With virtual inheritance, the MOST DERIVED class (DiamondTrap) initializes the virtual base (ClapTrap).

Stage 1 - Modify ScavTrap and FragTrap (add virtual):

ScavTrap.hpp
class ScavTrap : virtual public ClapTrap { // Add virtual
// ... rest unchanged
};
FragTrap.hpp
class FragTrap : virtual public ClapTrap { // Add virtual
// ... rest unchanged
};

Stage 2 - DiamondTrap declaration:

DiamondTrap.hpp
#ifndef DIAMONDTRAP_HPP
#define DIAMONDTRAP_HPP
#include "ScavTrap.hpp"
#include "FragTrap.hpp"
class DiamondTrap : public ScavTrap, public FragTrap {
private:
std::string _name; // DiamondTrap's own name (shadows ClapTrap::_name)
public:
DiamondTrap(std::string name);
DiamondTrap(const DiamondTrap& other);
DiamondTrap& operator=(const DiamondTrap& other);
~DiamondTrap();
using ScavTrap::attack; // Use ScavTrap's attack
void whoAmI();
};
#endif

Stage 3 - DiamondTrap implementation:

DiamondTrap.cpp
#include "DiamondTrap.hpp"
#include <iostream>
DiamondTrap::DiamondTrap(std::string name)
: ClapTrap(name + "_clap_name"), // Initialize virtual base FIRST
ScavTrap(name),
FragTrap(name),
_name(name)
{
// Take specific attributes from each parent
_hitPoints = FragTrap::_hitPoints; // 100 from FragTrap
_energyPoints = ScavTrap::_energyPoints; // 50 from ScavTrap
_attackDamage = FragTrap::_attackDamage; // 30 from FragTrap
std::cout << "DiamondTrap " << _name << " constructed" << std::endl;
}
DiamondTrap::~DiamondTrap() {
std::cout << "DiamondTrap " << _name << " destructed" << std::endl;
}
void DiamondTrap::whoAmI() {
std::cout << "I am " << _name << std::endl;
std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;
}
LineCodeWhy
virtual public ClapTrapVirtual inheritancePrevents duplicate ClapTrap
: ClapTrap(name + "_clap_name")Init virtual baseMost-derived class initializes virtual base
std::string _nameOwn memberShadows ClapTrap::_name
using ScavTrap::attackResolve ambiguityExplicitly choose ScavTrap’s attack
ClapTrap::_nameScope resolutionAccess base class member

1. Forgetting virtual on intermediate classes

// WRONG - two ClapTrap copies!
class ScavTrap : public ClapTrap { };
class FragTrap : public ClapTrap { };
// RIGHT - one shared ClapTrap
class ScavTrap : virtual public ClapTrap { };
class FragTrap : virtual public ClapTrap { };

2. Not initializing virtual base in DiamondTrap

// WRONG - ClapTrap not properly initialized!
DiamondTrap::DiamondTrap(std::string name)
: ScavTrap(name), FragTrap(name) { }
// RIGHT - must initialize ClapTrap explicitly
DiamondTrap::DiamondTrap(std::string name)
: ClapTrap(name + "_clap_name"),
ScavTrap(name),
FragTrap(name) { }

3. Confusing the two _name members

void DiamondTrap::whoAmI() {
// _name is DiamondTrap's own member
// ClapTrap::_name is the base class member
std::cout << _name << std::endl; // DiamondTrap name
std::cout << ClapTrap::_name << std::endl; // ClapTrap name (with suffix)
}
int main() {
std::cout << "=== Creating DiamondTrap ===" << std::endl;
DiamondTrap d("Diamond");
d.whoAmI(); // Shows both names
d.attack("Enemy"); // Uses ScavTrap's attack
d.guardGate(); // Inherited from ScavTrap
d.highFivesGuys(); // Inherited from FragTrap
std::cout << "=== End of main ===" << std::endl;
return 0;
}
// Expected construction order:
// ClapTrap Diamond_clap_name constructed
// ScavTrap Diamond constructed
// FragTrap Diamond constructed
// DiamondTrap Diamond constructed
//
// Expected destruction order (reverse):
// DiamondTrap Diamond destructed
// FragTrap Diamond destructed
// ScavTrap Diamond destructed
// ClapTrap Diamond_clap_name destructed
DiamondTrap.cpp
#include "DiamondTrap.hpp"
#include <iostream>
DiamondTrap::DiamondTrap(std::string name)
: ClapTrap(name + "_clap_name"),
ScavTrap(name),
FragTrap(name),
_name(name)
{
_hitPoints = FragTrap::_hitPoints;
_energyPoints = ScavTrap::_energyPoints;
_attackDamage = FragTrap::_attackDamage;
std::cout << "DiamondTrap " << _name << " constructed" << std::endl;
}
DiamondTrap::DiamondTrap(const DiamondTrap& other)
: ClapTrap(other),
ScavTrap(other),
FragTrap(other),
_name(other._name)
{
std::cout << "DiamondTrap " << _name << " copy constructed" << std::endl;
}
DiamondTrap& DiamondTrap::operator=(const DiamondTrap& other) {
ClapTrap::operator=(other);
_name = other._name;
return *this;
}
DiamondTrap::~DiamondTrap() {
std::cout << "DiamondTrap " << _name << " destructed" << std::endl;
}
void DiamondTrap::whoAmI() {
std::cout << "I am " << _name << std::endl;
std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;
}

// 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 { };

Continue your C++ journey:

Visit the Glossary for definitions of: