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.
Exercise 00: ClapTrap
Section titled “Exercise 00: ClapTrap”Subject Analysis
Section titled “Subject Analysis”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:
| Attribute | Value |
|---|---|
| Name | (constructor parameter) |
| Hit points | 10 |
| Energy points | 10 |
| Attack damage | 0 |
Approach Strategy
Section titled “Approach Strategy”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.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Class declaration:
#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);};
#endifStage 2 - Constructor with messages:
#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:
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;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
protected: | Access specifier | Derived classes can access, others can’t |
if (_energyPoints <= 0) | Resource check | Can’t act without energy |
_energyPoints-- | Consume resource | Actions cost energy |
unsigned int amount | Unsigned parameter | Damage can’t be negative |
Common Pitfalls
Section titled “Common Pitfalls”1. Using private instead of protected
// WRONG - derived classes can't access!private: std::string _name;
// RIGHT - derived classes can accessprotected: 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 firstvoid ClapTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { // Handle failure return; } _energyPoints--; // ...}Testing Tips
Section titled “Testing Tips”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;}Final Code
Section titled “Final Code”#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;}Exercise 01: ScavTrap
Section titled “Exercise 01: ScavTrap”Subject Analysis
Section titled “Subject Analysis”Create ScavTrap that inherits from ClapTrap with:
-
Different attribute values:
Attribute Value Hit points 100 Energy points 50 Attack damage 20 -
Custom attack message
-
New ability:
guardGate()
Approach Strategy
Section titled “Approach Strategy”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:
- ClapTrap constructor runs first
- Then ScavTrap constructor runs
Destruction is reverse:
- ScavTrap destructor runs first
- Then ClapTrap destructor runs
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Class declaration:
#ifndef SCAVTRAP_HPP#define SCAVTRAP_HPP
#include "ClapTrap.hpp"
class ScavTrap : public ClapTrap { // Inherits from ClapTrappublic: 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};
#endifStage 2 - Constructor with base initialization:
#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:
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;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
: public ClapTrap | Public inheritance | ScavTrap IS-A ClapTrap |
: ClapTrap(name) | Call base constructor | Initialize ClapTrap part first |
_hitPoints = 100 | Override value | ScavTrap has different stats |
void attack(...) | Override function | ScavTrap has custom attack message |
Common Pitfalls
Section titled “Common Pitfalls”1. Not calling base constructor
// WRONG - ClapTrap not properly initialized!ScavTrap::ScavTrap(std::string name) { _name = name; _hitPoints = 100; // ...}
// RIGHT - call base constructor firstScavTrap::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 bodyScavTrap::ScavTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; // Override after base construction}Testing Tips
Section titled “Testing Tips”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 destructedFinal Code
Section titled “Final Code”#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;}Exercise 02: FragTrap
Section titled “Exercise 02: FragTrap”Subject Analysis
Section titled “Subject Analysis”Create FragTrap that inherits from ClapTrap with:
| Attribute | Value |
|---|---|
| Hit points | 100 |
| Energy points | 100 |
| Attack damage | 30 |
New ability: highFivesGuys() - requests a high five.
Approach Strategy
Section titled “Approach Strategy”Same pattern as ScavTrap - this exercise reinforces inheritance concepts.
Progressive Code Building
Section titled “Progressive Code Building”#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#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;}Common Pitfalls
Section titled “Common Pitfalls”Same as ScavTrap - ensure you call the base constructor!
Testing Tips
Section titled “Testing Tips”int main() { FragTrap f("Frag"); f.attack("Enemy"); // Uses ClapTrap::attack (not overridden) f.highFivesGuys(); // FragTrap-only ability return 0;}Exercise 03: DiamondTrap
Section titled “Exercise 03: DiamondTrap”Subject Analysis
Section titled “Subject Analysis”Create DiamondTrap that inherits from BOTH ScavTrap AND FragTrap:
ClapTrap / \ ScavTrap FragTrap \ / DiamondTrapThis 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
Approach Strategy
Section titled “Approach Strategy”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).
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Modify ScavTrap and FragTrap (add virtual):
class ScavTrap : virtual public ClapTrap { // Add virtual // ... rest unchanged};class FragTrap : virtual public ClapTrap { // Add virtual // ... rest unchanged};Stage 2 - DiamondTrap declaration:
#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();};
#endifStage 3 - DiamondTrap implementation:
#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;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
virtual public ClapTrap | Virtual inheritance | Prevents duplicate ClapTrap |
: ClapTrap(name + "_clap_name") | Init virtual base | Most-derived class initializes virtual base |
std::string _name | Own member | Shadows ClapTrap::_name |
using ScavTrap::attack | Resolve ambiguity | Explicitly choose ScavTrap’s attack |
ClapTrap::_name | Scope resolution | Access base class member |
Common Pitfalls
Section titled “Common Pitfalls”1. Forgetting virtual on intermediate classes
// WRONG - two ClapTrap copies!class ScavTrap : public ClapTrap { };class FragTrap : public ClapTrap { };
// RIGHT - one shared ClapTrapclass 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 explicitlyDiamondTrap::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)}Testing Tips
Section titled “Testing Tips”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 destructedFinal Code
Section titled “Final Code”#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;}