Module 01 Tutorial
Prerequisites: Review Module 01 Concepts first.
Module 01 introduces memory management in C++. You’ll learn the critical difference between stack and heap allocation, understand references as aliases, and master pointers to member functions.
Exercise 00: BraiiiiiiinnnzzzZ
Section titled “Exercise 00: BraiiiiiiinnnzzzZ”Subject Analysis
Section titled “Subject Analysis”This exercise asks you to create a Zombie class with two functions:
newZombie(std::string name): Creates a zombie on the HEAP, returns a pointerrandomChump(std::string name): Creates a zombie on the STACK, announces itself
The key insight: when should memory survive beyond a function call?
Approach Strategy
Section titled “Approach Strategy”Step 1 - Understand the lifetime requirement
Ask yourself: “Does the caller need the zombie after the function returns?”
newZombie: YES → Heap allocation (survives function return)randomChump: NO → Stack allocation (auto-cleanup)
Step 2 - Design the Zombie class
- Private
_nameattribute - Constructor that sets name
- Destructor that prints “[name] is dead”
announce()method that prints “[name]: BraiiiiiiinnnzzzZ…”
Step 3 - Implement both allocation strategies
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Zombie class:
#ifndef ZOMBIE_HPP#define ZOMBIE_HPP
#include <string>
class Zombie {private: std::string _name;public: Zombie(std::string name); ~Zombie(); void announce() const;};
#endifStage 2 - Implementation:
#include "Zombie.hpp"#include <iostream>
Zombie::Zombie(std::string name) : _name(name) {}
Zombie::~Zombie() { std::cout << _name << " is dead." << std::endl;}
void Zombie::announce() const { std::cout << _name << ": BraiiiiiiinnnzzzZ..." << std::endl;}Stage 3 - The two allocation functions:
#include "Zombie.hpp"
Zombie* newZombie(std::string name) { return new Zombie(name); // Heap - caller must delete!}#include "Zombie.hpp"
void randomChump(std::string name) { Zombie zombie(name); // Stack - auto destroyed at function end zombie.announce();}// Destructor called HERE automaticallyLine-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
new Zombie(name) | Allocates on heap | Memory survives function return |
return new Zombie(...) | Returns pointer | Caller gets ownership |
Zombie zombie(name) | Stack allocation | Fast, automatic cleanup |
End of randomChump | Destructor runs | Stack variables cleaned up |
Common Pitfalls
Section titled “Common Pitfalls”1. Memory leaks with newZombie
// WRONG - memory leak!newZombie("Bob"); // Created but never deleted
// RIGHTZombie* z = newZombie("Bob");z->announce();delete z; // Clean up!2. Returning stack pointer (undefined behavior)
// WRONG - returning address of dead object!Zombie* badZombie(std::string name) { Zombie z(name); return &z; // z dies here, pointer is dangling!}3. Forgetting destructor message
The subject requires destructor to print. Evaluators will check!
Testing Tips
Section titled “Testing Tips”# Compilec++ -Wall -Wextra -Werror *.cpp -o zombie
# Test heap zombie./zombie# Should see: announce message, then later "X is dead" when deleted
# Watch for memory leaks with valgrindvalgrind --leak-check=full ./zombieFinal Code
Section titled “Final Code”#ifndef ZOMBIE_HPP#define ZOMBIE_HPP
#include <string>
class Zombie {private: std::string _name;public: Zombie(std::string name); ~Zombie(); void announce() const;};
Zombie* newZombie(std::string name);void randomChump(std::string name);
#endifExercise 01: Moar brainz!
Section titled “Exercise 01: Moar brainz!”Subject Analysis
Section titled “Subject Analysis”Create a function zombieHorde(int N, std::string name) that:
- Allocates N zombies in a single allocation
- Initializes all zombies with the same name
- Returns a pointer to the first zombie
Key constraint: ONE allocation, not N separate new calls.
Approach Strategy
Section titled “Approach Strategy”Step 1 - Use array allocation syntax
new Zombie[N] allocates N zombies contiguously.
Step 2 - Handle the default constructor requirement
Array allocation calls default constructor. You need to add one!
Step 3 - Initialize names after allocation
Since default constructor can’t take parameters, add a setName() method.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Add default constructor and setter:
class Zombie {private: std::string _name;public: Zombie(); // Default constructor for array allocation Zombie(std::string name); ~Zombie(); void setName(std::string name); // Set name after construction void announce() const;};Stage 2 - Implement zombieHorde:
#include "Zombie.hpp"
Zombie* zombieHorde(int N, std::string name) { if (N <= 0) return NULL;
// Single allocation for all N zombies Zombie* horde = new Zombie[N];
// Initialize each zombie's name for (int i = 0; i < N; i++) horde[i].setName(name);
return horde;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
new Zombie[N] | Array allocation | Single contiguous block |
horde[i].setName(name) | Initialize each | Default constructor left name empty |
return horde | Return first element | Array decays to pointer |
Common Pitfalls
Section titled “Common Pitfalls”1. Using delete instead of delete[]
Zombie* horde = new Zombie[N];
// WRONG - undefined behavior!delete horde;
// RIGHT - must use delete[] for arraysdelete[] horde;2. Making N separate allocations
// WRONG - N allocations, not one!for (int i = 0; i < N; i++) horde[i] = new Zombie(name);
// RIGHT - single allocationZombie* horde = new Zombie[N];3. Returning stack array
// WRONG - stack array dies at return!Zombie* zombieHorde(int N, std::string name) { Zombie horde[N]; // VLA on stack (also non-standard!) return horde; // Dangling pointer!}Testing Tips
Section titled “Testing Tips”# Test horde creation and cleanupint main() { Zombie* horde = zombieHorde(5, "Walker");
for (int i = 0; i < 5; i++) horde[i].announce();
delete[] horde; // Should see 5 "is dead" messages return 0;}Final Code
Section titled “Final Code”#include "Zombie.hpp"
Zombie* zombieHorde(int N, std::string name) { if (N <= 0) return NULL;
Zombie* horde = new Zombie[N];
for (int i = 0; i < N; i++) horde[i].setName(name);
return horde;}Exercise 02: HI THIS IS BRAIN
Section titled “Exercise 02: HI THIS IS BRAIN”Subject Analysis
Section titled “Subject Analysis”Demonstrate that references are aliases by showing:
- A string variable
- A pointer to that string
- A reference to that string
All three should print the same address and same value.
Approach Strategy
Section titled “Approach Strategy”This is a simple demonstration. The key insight: a reference IS the original variable, just with another name.
Progressive Code Building
Section titled “Progressive Code Building”#include <iostream>#include <string>
int main() { std::string str = "HI THIS IS BRAIN"; std::string* stringPTR = &str; // Pointer stores address std::string& stringREF = str; // Reference IS str
// Print addresses - all should be identical std::cout << "Address of str: " << &str << std::endl; std::cout << "Address in stringPTR: " << stringPTR << std::endl; std::cout << "Address of stringREF: " << &stringREF << std::endl;
// Print values - all should be identical std::cout << "Value of str: " << str << std::endl; std::cout << "Value via stringPTR: " << *stringPTR << std::endl; std::cout << "Value of stringREF: " << stringREF << std::endl;
return 0;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
std::string* stringPTR = &str | Store address | Pointer holds memory address |
std::string& stringREF = str | Create alias | Reference IS the original |
&str | Get address | Address-of operator |
&stringREF | Get address | Same as &str! |
*stringPTR | Dereference | Get value at address |
stringREF | Use directly | No dereference needed |
Common Pitfalls
Section titled “Common Pitfalls”1. Confusing & meanings
// In declaration: & means "reference type"std::string& ref = str;
// In expression: & means "address of"std::cout << &str;2. Thinking reference is a copy
std::string& ref = str;ref = "CHANGED";// str is also "CHANGED" - they're the same thing!Testing Tips
Section titled “Testing Tips”./brain# Output should show same address 3 times# Output should show same value 3 timesFinal Code
Section titled “Final Code”#include <iostream>#include <string>
int main() { std::string str = "HI THIS IS BRAIN"; std::string* stringPTR = &str; std::string& stringREF = str;
std::cout << &str << std::endl; std::cout << stringPTR << std::endl; std::cout << &stringREF << std::endl;
std::cout << str << std::endl; std::cout << *stringPTR << std::endl; std::cout << stringREF << std::endl;
return 0;}Exercise 03: Unnecessary Violence
Section titled “Exercise 03: Unnecessary Violence”Subject Analysis
Section titled “Subject Analysis”Create classes demonstrating when to use reference vs pointer as class member:
- HumanA: Always has a weapon → use reference
- HumanB: Might not have a weapon → use pointer
The weapon is shared - when it changes, both humans see the change.
Approach Strategy
Section titled “Approach Strategy”Step 1 - Understand the design decision
| HumanA | HumanB |
|---|---|
| Must have weapon at construction | May not have weapon |
| Weapon never changes | Weapon can be set later |
Use Weapon& (reference) | Use Weapon* (pointer) |
Step 2 - Reference initialization rule
References MUST be initialized at construction. Cannot be NULL.
Step 3 - Pointer can be NULL
Pointers can start as NULL and be assigned later.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Weapon class:
#ifndef WEAPON_HPP#define WEAPON_HPP
#include <string>
class Weapon {private: std::string _type;public: Weapon(std::string type); const std::string& getType() const; void setType(std::string type);};
#endifStage 2 - HumanA with reference:
class HumanA {private: std::string _name; Weapon& _weapon; // Reference - must always existpublic: HumanA(std::string name, Weapon& weapon); void attack() const;};HumanA::HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {} // MUST use initializer list!
void HumanA::attack() const { std::cout << _name << " attacks with their " << _weapon.getType() << std::endl;}Stage 3 - HumanB with pointer:
class HumanB {private: std::string _name; Weapon* _weapon; // Pointer - can be NULLpublic: HumanB(std::string name); void setWeapon(Weapon& weapon); void attack() const;};HumanB::HumanB(std::string name) : _name(name), _weapon(NULL) {}
void HumanB::setWeapon(Weapon& weapon) { _weapon = &weapon;}
void HumanB::attack() const { if (_weapon) std::cout << _name << " attacks with their " << _weapon->getType() << std::endl; else std::cout << _name << " has no weapon!" << std::endl;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
Weapon& _weapon | Reference member | Must exist, can’t be NULL |
: _weapon(weapon) | Initializer list | References MUST be initialized here |
Weapon* _weapon | Pointer member | Can be NULL, set later |
_weapon = &weapon | Store address | Convert reference to pointer |
if (_weapon) | NULL check | Pointer might be unset |
_weapon->getType() | Arrow operator | Dereference + member access |
Common Pitfalls
Section titled “Common Pitfalls”1. Not using initializer list for reference
// WRONG - reference not initialized!HumanA::HumanA(std::string name, Weapon& weapon) { _name = name; _weapon = weapon; // ERROR: _weapon already needed to exist!}
// RIGHT - use initializer listHumanA::HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {}2. Not checking NULL for pointer
// WRONG - crashes if no weapon!void HumanB::attack() const { std::cout << _weapon->getType(); // NULL dereference!}
// RIGHT - check firstif (_weapon) std::cout << _weapon->getType();Testing Tips
Section titled “Testing Tips”# Test that weapon changes affect both humansint main() { Weapon club("crude spiked club"); HumanA bob("Bob", club); HumanB jim("Jim");
jim.setWeapon(club); bob.attack(); jim.attack();
club.setType("some other type of club"); bob.attack(); // Should show new weapon! jim.attack(); // Should show new weapon!}Final Code
Section titled “Final Code”#include "Weapon.hpp"
Weapon::Weapon(std::string type) : _type(type) {}
const std::string& Weapon::getType() const { return _type;}
void Weapon::setType(std::string type) { _type = type;}Exercise 04: Sed is for losers
Section titled “Exercise 04: Sed is for losers”Subject Analysis
Section titled “Subject Analysis”Create a program that replaces all occurrences of string s1 with s2 in a file:
- Read file content
- Replace ALL occurrences (not just first)
- Write to
<filename>.replace - FORBIDDEN:
std::string::replace()function
Approach Strategy
Section titled “Approach Strategy”Step 1 - Read entire file into string
Use std::ifstream and std::getline() to read line by line.
Step 2 - Implement replacement without replace()
Use find() to locate occurrences, append() or substr() to build result.
Step 3 - Write to output file
Use std::ofstream to create <filename>.replace.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - The replacement algorithm:
std::string replaceAll(const std::string& content, const std::string& s1, const std::string& s2) { if (s1.empty()) return content; // Avoid infinite loop!
std::string result; std::size_t pos = 0; std::size_t found;
while ((found = content.find(s1, pos)) != std::string::npos) { result.append(content, pos, found - pos); // Before match result.append(s2); // Replacement pos = found + s1.length(); // Move past match } result.append(content, pos, std::string::npos); // Remainder
return result;}Stage 2 - File I/O:
int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " <file> <s1> <s2>" << std::endl; return 1; }
std::string filename = argv[1]; std::ifstream inFile(filename.c_str()); // .c_str() for C++98 if (!inFile.is_open()) { std::cerr << "Error: Cannot open file" << std::endl; return 1; }
// Read file content std::string content; std::string line; bool first = true; while (std::getline(inFile, line)) { if (!first) content += '\n'; content += line; first = false; } inFile.close();
// Replace and write std::string result = replaceAll(content, argv[2], argv[3]);
std::ofstream outFile((filename + ".replace").c_str()); outFile << result; outFile.close();
return 0;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
content.find(s1, pos) | Find next occurrence | Start searching from pos |
std::string::npos | ”Not found” value | Returned when no match |
result.append(content, pos, found - pos) | Append substring | Everything before match |
pos = found + s1.length() | Skip past match | Don’t re-find same occurrence |
.c_str() | String to C-string | C++98 file streams need this |
Common Pitfalls
Section titled “Common Pitfalls”1. Using forbidden std::string::replace()
// WRONG - forbidden!str.replace(pos, s1.length(), s2);
// RIGHT - use find + appendresult.append(content, pos, found - pos);result.append(s2);2. Not handling empty s1
// Without this check: infinite loop!if (s1.empty()) return content;3. Only replacing first occurrence
// WRONG - only finds firstsize_t pos = content.find(s1);// ... replace once
// RIGHT - loop until no more foundwhile ((found = content.find(s1, pos)) != std::string::npos) { // ... replace each}Testing Tips
Section titled “Testing Tips”# Create test fileecho "Hello World World" > test.txt
# Replace "World" with "42"./sed test.txt World 42
# Check resultcat test.txt.replace# Should show: "Hello 42 42"Final Code
Section titled “Final Code”#include <iostream>#include <fstream>#include <string>
std::string replaceAll(const std::string& content, const std::string& s1, const std::string& s2) { if (s1.empty()) return content;
std::string result; std::size_t pos = 0; std::size_t found;
while ((found = content.find(s1, pos)) != std::string::npos) { result.append(content, pos, found - pos); result.append(s2); pos = found + s1.length(); } result.append(content, pos, std::string::npos);
return result;}
int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " <file> <s1> <s2>" << std::endl; return 1; }
std::ifstream inFile(argv[1]); if (!inFile.is_open()) { std::cerr << "Error: Cannot open file" << std::endl; return 1; }
std::string content, line; bool first = true; while (std::getline(inFile, line)) { if (!first) content += '\n'; content += line; first = false; } inFile.close();
std::string result = replaceAll(content, argv[2], argv[3]);
std::ofstream outFile((std::string(argv[1]) + ".replace").c_str()); outFile << result; outFile.close();
return 0;}Exercise 05: Harl 2.0
Section titled “Exercise 05: Harl 2.0”Subject Analysis
Section titled “Subject Analysis”Create a Harl class that complains at different levels:
- DEBUG, INFO, WARNING, ERROR
- FORBIDDEN: if/else chains
The solution: use pointers to member functions.
Approach Strategy
Section titled “Approach Strategy”Step 1 - Declare member function pointer array
void (Harl::*funcs[4])();This declares an array of 4 pointers to Harl member functions.
Step 2 - Map levels to functions
Create parallel arrays: one for function pointers, one for level strings.
Step 3 - Loop and match
Find the matching level, call the corresponding function.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Harl class structure:
#ifndef HARL_HPP#define HARL_HPP
#include <string>
class Harl {private: void debug(); void info(); void warning(); void error();public: void complain(std::string level);};
#endifStage 2 - Using function pointers:
void Harl::complain(std::string level) { // Array of member function pointers void (Harl::*funcs[4])() = { &Harl::debug, &Harl::info, &Harl::warning, &Harl::error };
// Parallel array of level names std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
// Find and call for (int i = 0; i < 4; i++) { if (level == levels[i]) { (this->*funcs[i])(); // Call member function via pointer return; } }}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
void (Harl::*funcs[4])() | Declare array | 4 pointers to Harl methods |
&Harl::debug | Get function address | Address-of member function |
(this->*funcs[i])() | Call via pointer | Dereference + call |
Common Pitfalls
Section titled “Common Pitfalls”1. Using if/else forest
// WRONG - forbidden!if (level == "DEBUG") debug();else if (level == "INFO") info();// ...
// RIGHT - use function pointers(this->*funcs[i])();2. Wrong syntax for member function pointer call
// WRONGfuncs[i](); // Missing thisthis->funcs[i](); // Wrong syntax*funcs[i](); // Wrong syntax
// RIGHT(this->*funcs[i])();Testing Tips
Section titled “Testing Tips”./harl DEBUG# Should print debug message
./harl ERROR# Should print error message
./harl INVALID# Should print nothing (or handle gracefully)Final Code
Section titled “Final Code”#include "Harl.hpp"#include <iostream>
void Harl::debug() { std::cout << "[ DEBUG ]" << std::endl; std::cout << "I love having extra bacon..." << std::endl;}
void Harl::info() { std::cout << "[ INFO ]" << std::endl; std::cout << "I cannot believe adding extra bacon costs more..." << std::endl;}
void Harl::warning() { std::cout << "[ WARNING ]" << std::endl; std::cout << "I think I deserve to have some extra bacon for free..." << std::endl;}
void Harl::error() { std::cout << "[ ERROR ]" << std::endl; std::cout << "This is unacceptable! I want to speak to the manager now." << std::endl;}
void Harl::complain(std::string level) { void (Harl::*funcs[4])() = { &Harl::debug, &Harl::info, &Harl::warning, &Harl::error }; std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) { if (level == levels[i]) { (this->*funcs[i])(); return; } }}Exercise 06: Harl Filter
Section titled “Exercise 06: Harl Filter”Subject Analysis
Section titled “Subject Analysis”Create a log filter using switch with fall-through:
- Given a minimum level, print that level AND all levels above it
- DEBUG → prints DEBUG, INFO, WARNING, ERROR
- WARNING → prints WARNING, ERROR
- Invalid → prints special message
Approach Strategy
Section titled “Approach Strategy”Step 1 - Convert string to int
C++98 can’t switch on strings. Convert level name to index (0-3).
Step 2 - Use fall-through
Don’t use break between cases - let execution “fall through” to subsequent cases.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Convert level to int:
int getLevel(const std::string& level) { std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"}; for (int i = 0; i < 4; i++) if (level == levels[i]) return i; return -1; // Invalid level}Stage 2 - Switch with fall-through:
switch (getLevel(argv[1])) { case 0: harl.debug(); // NO BREAK - fall through! case 1: harl.info(); // NO BREAK - fall through! case 2: harl.warning(); // NO BREAK - fall through! case 3: harl.error(); break; // Only break at the end default: std::cout << "[ Probably complaining about insignificant problems ]" << std::endl;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
case 0: | DEBUG level | Entry point for DEBUG |
No break | Fall-through | Continue to next case |
case 3: ... break; | ERROR level | Stop after ERROR |
default: | Invalid level | Catch-all |
Common Pitfalls
Section titled “Common Pitfalls”1. Adding break to each case
// WRONG - doesn't filter correctly!case 0: harl.debug(); break; // Stops here, doesn't show INFO/WARNING/ERROR
// RIGHT - no break for fall-throughcase 0: harl.debug(); // fall throughcase 1: harl.info();2. Trying to switch on string
// WRONG - C++98 doesn't support this!switch (level) { // level is std::string case "DEBUG": // ...}
// RIGHT - convert to int firstswitch (getLevel(level)) { case 0: // ...}Testing Tips
Section titled “Testing Tips”./harlFilter DEBUG# Should print: DEBUG, INFO, WARNING, ERROR
./harlFilter WARNING# Should print: WARNING, ERROR
./harlFilter ERROR# Should print: ERROR only
./harlFilter INVALID# Should print: [ Probably complaining about insignificant problems ]Final Code
Section titled “Final Code”#include "Harl.hpp"#include <iostream>
int getLevel(const std::string& level) { std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"}; for (int i = 0; i < 4; i++) if (level == levels[i]) return i; return -1;}
int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <level>" << std::endl; return 1; }
Harl harl;
switch (getLevel(argv[1])) { case 0: harl.debug(); case 1: harl.info(); case 2: harl.warning(); case 3: harl.error(); break; default: std::cout << "[ Probably complaining about insignificant problems ]" << std::endl; }
return 0;}