✅ Status: Completed – all exercises
🏫 School: 42 – C++ Modules (Module 04)
🏅 Score: 100/100
Subtype polymorphism, virtual functions, deep copies, abstract classes, and “interfaces” via pure abstract classes (C++98).
- Description
- Goals of the Module
- Concept Notes
- Exercises Overview
- Requirements
- Build & Run
- Repository Layout
- Testing Tips
- 42 Notes
This repository contains my solutions to 42’s C++ Module 04 (C++98). The module focuses on runtime polymorphism (virtual functions), proper destruction through base pointers, deep-copy ownership (Rule of Three), plus abstract classes and interface-like design using pure virtual methods.
Concepts covered (depending on the exercise):
- Subtype polymorphism: virtual functions, virtual dispatch, overriding
- Correct destruction: virtual destructors when deleting through base pointers
- Composition + ownership:
Brain*insideDog/Cat+ propernew/delete - Deep copy vs shallow copy (copy constructor + assignment operator)
- Abstract classes (non-instantiable base)
- “Interfaces” via pure abstract classes (
= 0)
If you ever delete an object through a base pointer, the base class must have a virtual destructor.
Animal* a = new Dog();
delete a; // must call ~Dog() then ~Animal()- If
~Animal()is not virtual, the program may call only~Animal(). - Result: derived cleanup is skipped → leaks (e.g.
Brain*) and/or undefined behavior.
✅ Practical rule:
- If a class is meant to be used polymorphically (has virtual methods), give it a virtual destructor.
When a class owns heap memory (like Brain*), copying must be a deep copy.
Shallow copy (bad): copies only the pointer.
this->brain = other.brain; // ❌ both objects share the same BrainProblems:
- shared state (changing ideas in one changes the other)
- double
delete(crash)
Deep copy (good): allocates a new resource and copies the content.
this->brain = new Brain(*other.brain); // ✅ independent copyThis is why ex01/ex02 naturally push you to the Rule of Three:
- destructor
- copy constructor
- copy assignment operator
In C++98 we don’t have a special interface keyword — we build it using pure abstract classes.
Abstract class (common at 42):
- has at least one pure virtual method (
= 0) - cannot be instantiated
- may contain data and some shared implementation
class Animal {
public:
virtual ~Animal() {}
virtual void makeSound() const = 0; // pure
};Interface-style class (ex03 style):
- usually only pure virtual methods
- no data
- always a virtual destructor
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;
};Key fact: once a method is virtual in the base class, it stays virtual in all derived classes — even if you don’t repeat the word virtual.
class Animal { public: virtual void makeSound() const; };
class Dog : public Animal {
public:
void makeSound() const; // ✅ still virtual (because Animal’s is virtual)
// virtual void makeSound() const; // ✅ optional, for readability
};So why do people still write virtual again?
- Readability: makes it obvious this method is part of polymorphism.
- Consistency in headers.
But repeating virtual does not protect you from the most common bug:
- signature mismatch (accidentally not overriding)
Example:
class Animal { public: virtual void makeSound() const; };
class Dog : public Animal {
public:
void makeSound(); // ❌ not overriding (missing const) → creates a different function
};In modern C++ (C++11+), you’d write:
void makeSound() const override;That forces the compiler to error if it doesn’t actually override.
In C++98, override does not exist, so common alternatives are:
-
repeat the exact signature carefully (including
const) -
optionally write a comment:
virtual void makeSound() const; // override
Goal:
Implement a base class Animal with a type and a makeSound() method.
Create derived classes Dog and Cat that override makeSound() so calls through Animal* produce the derived sound.
Also implement WrongAnimal / WrongCat to demonstrate what happens when polymorphism is done “wrong” (e.g., missing virtual).
Concepts practiced:
virtualfunctions and overriding- Polymorphic behavior via base pointers/references
- Why forgetting
virtualbreaks dynamic dispatch
Goal:
Add a Brain class with std::string ideas[100].
Dog and Cat must own a Brain* and manage it with new / delete.
In main, create an array of Animal* (half dogs / half cats), then delete them via Animal* and ensure destructors are called correctly and there are no leaks.
Copies of Dog and Cat must be deep copies (no shared Brain).
Concepts practiced:
- Rule of Three (copy ctor, assignment, destructor)
- Deep copy ownership & avoiding double free / leaks
- Polymorphic deletion order
Goal:
Make the base Animal non-instantiable (because an “Animal” doesn’t make sense by itself). Everything should still work as before with Dog / Cat.
Optional: rename it to
AAnimal(depending on your preference / implementation).
Concepts practiced:
- Pure virtual functions
- Abstract base classes and intent in API design
Goal: Implement a small Materia system:
-
AMateria(abstract base): hastype,clone()(pure virtual), anduse() -
Concrete materias:
IceandCuretype:"ice"/"cure"clone()returns a new instance of the same type
-
ICharacterinterface + concreteCharacterwith an inventory of 4 slotsequip()puts materia into the first empty slotunequip()must not delete materia- copies of
Charactermust be deep (inventory cloned, old deleted)
-
IMateriaSourceinterface +MateriaSource- can learn up to 4 materia “templates” (store copies)
- can
createMateria(type)by cloning learned template, or return0if unknown
Important note: Unequipped materias must still be managed somehow (save addresses, “floor” storage, etc.) to avoid leaks.
Concepts practiced:
- Interface design in C++98 (pure abstract classes)
- Cloning pattern / prototype pattern
- Ownership rules and memory safety
From the subject:
- Compiler:
c++ - Flags:
-Wall -Wextra -Werror(and it must compile with-std=c++98) - No external libraries
- Forbidden:
printf,malloc,free(and family) - Until later modules: no STL containers / algorithms (no
<vector>, no<algorithm>, etc.) - “Goodbye Norminette!” (no enforced C norm), but code must stay readable
git clone <this-repo-url>
cd cpp-module-04cd ex00
make
./animalcd ex01
make
./animalcd ex02
make
./animalcd ex03
make
./interfaceExecutable names may vary depending on your Makefiles.
cpp-module-04/
├── ex00/
│ ├── Makefile
│ ├── main.cpp
│ ├── Animal.hpp / Animal.cpp
│ ├── Dog.hpp / Dog.cpp
│ ├── Cat.hpp / Cat.cpp
│ ├── WrongAnimal.hpp / WrongAnimal.cpp
│ └── WrongCat.hpp / WrongCat.cpp
│
├── ex01/
│ ├── Makefile
│ ├── main.cpp
│ ├── Animal.* Dog.* Cat.*
│ └── Brain.hpp / Brain.cpp
│
├── ex02/
│ ├── Makefile
│ ├── main.cpp
│ ├── AAnimal/Animal.* Dog.* Cat.* Brain.*
│ └── (same structure as ex01, but base is abstract)
│
└── ex03/
├── Makefile
├── main.cpp
├── AMateria.* Ice.* Cure.*
├── ICharacter.hpp Character.*
├── IMateriaSource.hpp MateriaSource.*
└── (optional) Floor/Trash manager files
-
ex00
- Call
makeSound()throughAnimal*and verify the derived sound is used - Verify
WrongCatbehaves likeWrongAnimalwhen polymorphism is broken
- Call
-
ex01 / ex02
-
Copy a
Dog/Cat, modify ideas in one Brain → the other must not change (deep copy) -
Run leak checks (example):
valgrind --leak-check=full ./animal
-
Ensure destructors are called in correct order when deleting through
Animal*
-
-
ex03
- Test inventory boundaries (equip 5th materia → nothing happens)
- Test
unequip()does not delete; ensure you still free unequipped materias later - Copy a
Characterand confirm deep copy (materias cloned; originals independent) MateriaSourcecreates only known types; unknown → returns0
- Outputs from constructors/destructors should be distinct per class to make tests readable.
- This module is heavily about ownership rules — if you
new, you mustdeleteexactly once. - You can pass the module without ex03, but doing it is excellent practice for interfaces + memory management.
If you’re a 42 student working on the same module: get inspired, but write your own implementation — that’s where the learning happens. 🚀