This project has been created as part of the 42 curriculum by baelgadi.
Inheritance
The ring chooses you, but the oath came before you.
Every Lantern in the Corps inherits the same light.
A class that inherits is a Lantern accepting the ring, and with it comes every power of the one before it and… every duty too.
|
CPP Module 03:
4 exercises. C++98 standard only. Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98Exercise 00: Aaaaand… Open! · ex00/ · ClapTrap, the base ringExercise 01: Serena, my love! · ex01/ · ScavTrap inherits from ClapTrapExercise 02: Repetitive work · ex02/ · FragTrap inherits from ClapTrapExercise 03: Now it’s weird! · ex03/ · DiamondTrap inherits from bothForbidden: using namespace (-42) · friend (-42) · printf · *alloc · free · STL containers · STL algorithms · function bodies in headersRequired: Orthodox Canonical Form on every class · every constructor and destructor must print |
The Corps passes the ring down.
Each new Lantern wakes up already knowing how to fly, already knowing how to bind a construct.
class ClapTrap {
protected:
std::string name;
int hitPoints;
int energyPoints;
int attackDamage;
};
class ScavTrap : public ClapTrap { /* gets everything above for free */ };ScavTrap does not redeclare hitPoints. It already has one (the one inherited from ClapTrap).
Redeclaring would be forging a second ring for the same finger.
The hierarchy:
[!NOTE] Always use
publicinheritance for CPP03.
privateinheritance would lock the parent’s public members away from anyone outside the child.
The Guardians of Oa keep some knowledge for themselves, some for the Corps, and some for everyone in the galaxy.
| Specifier | Who can touch it | meaning… |
|---|---|---|
| public | Everyone | civilians, allies, enemies |
| protected | The Corps | this class and its descendants |
| private | The Guardians alone | this class and nothing else |
// In ClapTrap.hpp
protected:
std::string name;
int hitPoints;
int energyPoints;
int attackDamage;If hitPoints stayed private, ScavTrap’s constructor could not write this->hitPoints = 100.
[!WARNING] A child class does NOT redeclare the parent’s attributes.
There is onehitPoints, inherited.
Declaring it again in ScavTrap creates a second, separate field.
CPP02 taught the 4: default constructor, copy constructor, copy assignment, destructor.
CPP03 adds one rule on top.
When you copy a child, you must copy the parent’s part too.
// Copy constructor
ScavTrap::ScavTrap(ScavTrap const &src) : ClapTrap(src)
{
// ClapTrap(src) copies the inherited fields
// anything ScavTrap-specific would be copied here
}
// Copy assignment: same idea
ScavTrap &ScavTrap::operator=(ScavTrap const &rhs)
{
if (this != &rhs)
ClapTrap::operator=(rhs);
return *this;
}Forget ClapTrap(src) and the base portion gets default-constructed instead of copied!
The new Lantern wakes up with a blank ring while you thought you were cloning a veteran.
The oath is recited from the base up. Otherwise the ring will not power on.
ScavTrap::ScavTrap(std::string const &name) : ClapTrap(name)
{
this->hitPoints = 100;
this->energyPoints = 50;
this->attackDamage = 20;
std::cout << "ScavTrap " << name << " constructed" << std::endl;
}The : ClapTrap(name) is the initializer list. It runs before the body.
ClapTrap is built, prints its message, then ScavTrap takes over.
For ScavTrap s("GuyGardner"):
1. ClapTrap("GuyGardner") constructed ← base first
2. ScavTrap("GuyGardner") constructed ← derived after
...
3. ScavTrap "GuyGardner" destroyed ← derived first
4. ClapTrap "GuyGardner" destroyed ← base last
| Phase | Order |
|---|---|
| Construction | Deepest parent → most derived |
| Destruction | Most derived → deepest parent |
Parallax is what happens when one being is split in two.
The diamond problem is the same idea in code.
Without virtual, DiamondTrap contains 2 ClapTrap subobjects (one from each parent).
this->hitPoints becomes ambiguous.
Which ClapTrap? Which ring? The compiler refuses.
The fix is willpower made into a keyword.
class ScavTrap : virtual public ClapTrap { };
class FragTrap : virtual public ClapTrap { };
class DiamondTrap : public ScavTrap, public FragTrap { };virtual public ClapTrap tells the compiler: there is only ever one ClapTrap inside any descendant, no matter how many paths lead to it.
When a virtual base is involved, the most derived class initializes it directly.
Intermediate classes calls to that base are ignored.
DiamondTrap::DiamondTrap(std::string const &name)
: ClapTrap(name + "_clap_name"), // DiamondTrap calls ClapTrap directly
ScavTrap(name), // the ClapTrap(name) call from here is SKIPPED
FragTrap(name), // the ClapTrap(name) call from here is SKIPPED
name(name) // DiamondTrap's own private name
{
this->hitPoints = 100; // from FragTrap
this->energyPoints = 50; // from ScavTrap value
this->attackDamage = 30; // from FragTrap
std::cout << "DiamondTrap " << name << " constructed" << std::endl;
}[!WARNING] ScavTrap sets
EP=50. FragTrap runs after and overwritesEP=100.
The subject demandsEP=50so we have to manually correct it in DiamondTrap’s body.
Construction order for DiamondTrap:
1. ClapTrap("Diamond_clap_name") ← virtual base, called directly
2. ScavTrap("Diamond") ← its ClapTrap() ignored
3. FragTrap("Diamond") ← its ClapTrap() ignored
4. DiamondTrap("Diamond") ← corrects EP back to 50
Hal Jordan and Green Lantern of 2814 are the same man. Both names exist, both are real.
Scope tells you which one is being called.
class ClapTrap {
protected:
std::string name; // ClapTrap::name → "Diamond_clap_name"
};
class DiamondTrap : public ScavTrap, public FragTrap {
private:
std::string name; // DiamondTrap::name → "Diamond"
};The subject demands this intentional shadowing.
It even hints at the -Wshadow flag, not as a warning to fix, but as proof you know what you are doing.
void DiamondTrap::whoAmI(void)
{
std::cout << "DiamondTrap name: " << this->name << std::endl;
std::cout << "ClapTrap name: " << ClapTrap::name << std::endl;
}this->name → DiamondTrap’s private member, the plain nameClapTrap::name → the base’s protected member, the _clap_name versionFor ClapTrap::name to reach DiamondTrap, ClapTrap’s name must be protected. Another reason private would have broken the module.
[!NOTE] Since we are compiling with
c++the-Wshadowdoesn’t warn on derived members shadows base member case.
clang++has-Wshadow-allfor this.
# ──── ex00 ────
cd ex00 && make
./claptrap
# ──── ex01 ────
# Verify: "ClapTrap X constructed" before "ScavTrap X constructed"
# Verify: ScavTrap attack message != ClapTrap attack message
cd ex01 && make
./scavtrap
# ──── ex02 ────
# Same chain check, but for FragTrap
# Verify: highFivesGuys() prints a message
cd ex02 && make
./fragtrap
# ──── ex03 ────
# Verify: ClapTrap → ScavTrap → FragTrap → DiamondTrap on construction
# Verify: DiamondTrap → FragTrap → ScavTrap → ClapTrap on destruction
# Verify: whoAmI() shows both "Diamond" and "Diamond_clap_name"
# Verify: 50 energy points
cd ex03 && make
./diamondtrapAttribute verification for DiamondTrap:
DiamondTrap d("D");
for (int i = 0; i < 50; i++)
d.attack("x");
d.attack("x"); // must print "no energy"You can inherit the ring. You can stand in the Corps line and look the part.
But the diamond is what tells the Guardians whether you understood any of it.
In brightest day, in blackest night, no ClapTrap shall be constructed twice.