Skip to content

Module 03 Tutorial

Prerequisites: Review Module 03 Concepts first.

Module 03 introduces inheritance through the ClapTrap robot family. You’ll build a hierarchy of classes, culminating in the infamous “diamond problem” with virtual inheritance.


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