This project has been created as part of the 42 curriculum by baelgadi.

CPP Module 04

Subtype polymorphism, abstract classes and interfaces


The Helmet of Nabu does not care whose head it rests on.
Kent Nelson, Khalid, Eric and Linda. Different mortals, one same call to power.

A base-class pointer is the Helmet. The wearer underneath decides what the magic actually does.


Previously…


CPP Module 04:

4 exercises. C++98 standard only.
Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98

Exercise 00: Polymorphism · ex00/ · Animal/Dog/Cat + the WrongAnimal contrast
Exercise 01: I don’t want to set the world on fire · ex01/ · Brain + deep copy
Exercise 02: Abstract class · ex02/ · Animal becomes pure virtual
Exercise 03: Interface & recap · ex03/ · AMateria, Character, MateriaSource

Forbidden: using namespace (-42) · friend (-42) · printf · *alloc · free · STL containers · STL algorithms · function bodies in headers
Required: Orthodox Canonical Form on every non-interface class · a virtual destructor on every polymorphic base · specific construction/destruction messages

Subtype Polymorphism

When the Helmet speaks, it is never the Helmet’s voice. It is the wearer’s.
You hold an Animal*, but the Dog underneath is the one that answers.

class Animal {
public:
    virtual void makeSound() const;   // the call goes through the wearer
};

const Animal* j = new Dog();
const Animal* i = new Cat();

j->makeSound();   // Dog speaks
i->makeSound();   // Cat speaks

The keyword virtual is the enchantment.
It tells the compiler to look at what the object really is at runtime, not at the type of the pointer holding it.

Static type Dynamic type
What it is The type written in the code (Animal*) The real object behind it (Dog)
Decided at Compile time Run time
Used by virtual No Yes
Used by normal calls Yes No

Remove the enchantment and the Helmet speaks for itself.
That is exactly what WrongAnimal demonstrates:

class WrongAnimal {
public:
    void makeSound() const;   // NOT virtual
};

const WrongAnimal* w = new WrongCat();
w->makeSound();   // WrongAnimal speaks — the wearer is ignored

WrongCat wc;
wc.makeSound();   // only as a concrete WrongCat does the cat speak

[!NOTE] The return type of an overriding function does not need to repeat virtual, but writing it on every override documents intent.
Once a function is virtual in the base, it is virtual in every derived class whether you type the word or not.


Virtual Destructors

A wearer is more than the Helmet. There is a mind underneath.
When you put the Helmet away, you must also let that mind go.

class Animal {
public:
    virtual ~Animal();   // mandatory the moment deletion happens through a base pointer
};

const Animal* j = new Dog();
delete j;   // Dog::~Dog() runs first, THEN Animal::~Animal()

Without the virtual, delete j only calls ~Animal().
Dog’s destructor never runs, the Brain it owns is never freed, and you leak a soul on every delete.

Dog destructor called      ← derived first
Animal destructor called   ← base last

[!WARNING] Non-virtual destruction through a base pointer is undefined behavior, not just a leak.
If a class has a single virtual function, give it a virtual destructor. There is no reason not to.


Abstract Classes

The Helmet of Nabu cannot walk the earth on its own. It must be worn.
In ex02, Animal becomes something you can never instantiate directly.

class Animal {
public:
    virtual void makeSound() const = 0;   // pure virtual — the "= 0" is mandatory
};

The = 0 makes makeSound() a pure virtual function.
A class with even one pure virtual function is abstract: it has no complete form of its own.

Animal test;   // compile error: cannot declare variable 'test' to be of abstract type 'Animal'
Concrete class Abstract class
Has a pure virtual? No Yes (at least one)
Can you instantiate it? Yes No
Can you hold a pointer to it? Yes Yes
Role A wearer (Dog, Cat) The Helmet itself

Dog and Cat stay exactly as they were in ex01. They override makeSound(), so they are complete and they can be worn.

[!NOTE] An abstract class still has constructors and a destructor. They run when a derived object is built and destroyed.
You just cannot create the base alone.


Deep Copy & the Brain

Every host carries their own mind. Copy the wearer and you must copy the mind too, not share it.
In ex01, Dog and Cat each own a Brain* allocated with new.

class Dog : public Animal {
private:
    Brain* _brain;   // owned, heap-allocated
};

// Copy constructor — a NEW brain, deep-copied
Dog::Dog(const Dog& other) : Animal(other), _brain(new Brain(*other._brain)) {}

// Copy assignment — free the old brain, clone the new one
Dog& Dog::operator=(const Dog& other) {
    if (this != &other) {
        Animal::operator=(other);
        delete _brain;
        _brain = new Brain(*other._brain);
    }
    return *this;
}

The evaluator’s test, and the reason OCF matters here:

Dog basic;
{
    Dog tmp = basic;   // deep copy → tmp gets its OWN brain
}                      // tmp dies, deletes ITS brain — basic is untouched
basic.getBrain();      // still valid

[!WARNING] A shallow copy makes tmp and basic share one Brain.
When tmp leaves the scope it deletes that shared brain, and basic is left pointing at freed memory → double free on its own destruction.


Interfaces

The Lords of Order do not hand you an implementation. They hand you a pact: these powers must exist.
An interface is a class that is only pure virtual functions plus a virtual destructor. No data, no bodies.

class ICharacter {
public:
    virtual ~ICharacter() {}
    virtual std::string const& getName() const = 0;
    virtual void equip(AMateria* m) = 0;
    virtual void unequip(int idx) = 0;
    virtual void use(int idx, ICharacter& target) = 0;
};

class IMateriaSource {
public:
    virtual ~IMateriaSource() {}
    virtual void learnMateria(AMateria*) = 0;
    virtual AMateria* createMateria(std::string const& type) = 0;
};

Character implements ICharacter. MateriaSource implements IMateriaSource.
Anyone holding an ICharacter* knows the pact will be honored, without knowing or caring which concrete class fulfills it.

[!NOTE] Interfaces are the only classes in CPP04 exempt from full Orthodox Canonical Form.
The eval checks OCF on every other class — AMateria, Ice, Cure, Character, MateriaSource.


clone() & the Materia

A spell is not moved from hand to hand. It is copied, and the copy is what you carry.
AMateria is abstract because its clone() is pure virtual: the base cannot copy itself, only a concrete spell knows how.

class AMateria {
public:
    virtual AMateria* clone() const = 0;   // pure → AMateria stays abstract
    virtual void use(ICharacter& target);
protected:
    std::string _type;
};

AMateria* Ice::clone()  const { return new Ice(*this); }   // a brand-new, independent Ice
AMateria* Cure::clone() const { return new Cure(*this); }

createMateria("ice") returns a clone() of a learned template. equip() takes ownership of that pointer. Whoever owns it, frees it.

[!WARNING] unequip() must not delete the materia. It drops it on the floor and the caller becomes responsible for it.
The matching trap: equip() / learnMateria() on a full container must not silently leak the pointer they were handed. Free the overflow, or hand ownership back to the caller, but never lose it.


Traps

Calling a non-virtual method through a base pointer
Without virtual, animal->makeSound() calls Animal::makeSound() no matter what the object really is. That is the whole point of WrongAnimal — it is the wrong way, on purpose.

Missing virtual ~Animal()

const Animal* a = new Dog();
delete a;   // only ~Animal() runs → Dog's Brain leaks. UB, not just a leak.

Shallow copy of a Brain owner

// WRONG — shared brain, double free
Dog::Dog(const Dog& o) : Animal(o), _brain(o._brain) {}

// RIGHT — independent brain
Dog::Dog(const Dog& o) : Animal(o), _brain(new Brain(*o._brain)) {}

Trying to instantiate an abstract class
Animal test; must fail to compile once makeSound() is = 0. If it compiles, the class is not actually abstract.

Leaking on a full inventory or full source (ex03)
Equipping a 5th materia into a 4-slot Character, or teaching a 5th to a MateriaSource, must not leak the pointer. Either delete it on overflow, or make sure the caller deletes it.

Empty copy assignment in OCF
The eval rejects empty/cosmetic OCF. A copy assignment that prints a message but copies nothing is a fail — deep-copy the owned resources.


Instructions

# ──── ex00 ────
# Verify: Animal* on a Dog/Cat calls the DERIVED makeSound()
# Verify: WrongAnimal* on a WrongCat calls the BASE (non-virtual) sound
cd ex00 && make
./ex00
valgrind --leak-check=full ./ex00

# ──── ex01 ────
# Verify: copying a Dog/Cat gives it its OWN Brain (different address)
# Verify: deleting an Animal* to a Dog frees the Brain (no leak)
cd ex01 && make
./ex01
valgrind --leak-check=full ./ex01

# ──── ex02 ────
# Verify: 'Animal test;' fails to compile (abstract type)
# Verify: Dog and Cat still behave exactly like ex01
cd ex02 && make
./ex02
valgrind --leak-check=full ./ex02

# ──── ex03 ────
# Verify: subject example prints the ice bolt then the heal
# Verify: createMateria("unknown") returns NULL
# Verify: Character copy/assignment deep-copy the inventory
cd ex03 && make
./ex03
valgrind --leak-check=full ./ex03

Abstractness check for ex02:

Animal test;   // must NOT compile: "cannot declare variable 'test' to be of abstract type 'Animal'"

Deep-copy check for ex01:

Dog basic;
basic.getBrain()->ideas[0] = "Fetch the ball";
Dog tmp = basic;
// tmp.getBrain() != basic.getBrain()  → independent brains

Resources


You can hold the Helmet. You can put it on and look the part.
But it is the wearer underneath, the override, the deep copy, the freed soul, that decides whether the magic is real.

Whoever puts on the Helmet answers a call older than themselves. Make sure your destructor is virtual when they take it off.