Module 01: Memory Allocation, References, and Pointers
Key Concepts:
- Stack vs Heap allocation
newanddeleteoperators- References vs Pointers
- Pointers to members
switchstatements
Why This Matters
Section titled “Why This Matters”Understanding memory is crucial for writing correct, efficient C++ code. Here’s why these concepts matter:
Why Stack vs Heap?
Section titled “Why Stack vs Heap?”The stack is fast but limited—like a small desk where you work on the current task. The heap is larger but requires manual management—like a warehouse where you store long-term items.
Why References Over Pointers?
Section titled “Why References Over Pointers?”References are safer aliases for existing variables. They can’t be null and can’t be reassigned. Use them when you need to pass large objects efficiently without copying.
Why new/delete Over malloc/free?
Section titled “Why new/delete Over malloc/free?”In C++, new and delete do more than just allocate memory—they call constructors and destructors. This ensures objects are properly initialized and cleaned up.
Understanding New Operators in This Module
Section titled “Understanding New Operators in This Module”This module introduces memory management operators. Here’s what they mean:
The new Operator
Section titled “The new Operator”int* ptr = new int; // Allocate memory for one intint* arr = new int[10]; // Allocate memory for 10 intsnewallocates memory on the heap (also called “free store”)- It returns a pointer to the allocated memory
- Memory allocated with
newpersists until you explicitlydeleteit - Unlike
malloc(),newautomatically calculates the size and properly initializes objects
The delete Operator
Section titled “The delete Operator”delete ptr; // Free memory for one objectdelete[] arr; // Free memory for array (NOTE the []!)deletefrees memory that was allocated withnewdelete[](with square brackets) frees arrays allocated withnew[]- CRITICAL: Using
deleteon an array is undefined behavior! - CRITICAL: Using
delete[]on a single object is undefined behavior!
References with &
Section titled “References with &”int& ref = x; // ref is an alias (another name) for x&after a type creates a reference (not a pointer)- A reference is just another name for an existing variable
- When you change
ref, you changex- they are the same variable! - References must be initialized when declared
- References cannot be reassigned to refer to a different variable
Pointer Syntax Review
Section titled “Pointer Syntax Review”int* ptr = &x; // ptr stores the address of x (ptr "points to" x)*ptr = 42; // Dereference ptr to access/modify xptr->method(); // If ptr points to an object, call its method- Remember:
*before a pointer dereferences it (accesses the value) &before a variable gets its address->is shorthand for dereference + member access
The ->* Operator (Pointer to Member)
Section titled “The ->* Operator (Pointer to Member)”void (Class::*ptr)() = &Class::method; // ptr points to a member function(object.*ptr)(); // Call through object(objectPtr->*ptr)(); // Call through pointer to object->*combines the arrow operator with pointer-to-member- It’s used when you have a pointer to an object AND a pointer to one of its members
- This is advanced syntax used for function pointers to member functions
Understanding Memory Layout (Visual Guide)
Section titled “Understanding Memory Layout (Visual Guide)”Understanding how memory is organized helps you write better C++ code:
Memory Layout Overview
Section titled “Memory Layout Overview”┌─────────────────────────────────────┐│ HIGH MEMORY ││ ││ ┌──────────────────────────────┐ ││ │ HEAP │ │ ← Dynamic allocation (new/delete)│ │ (grows downward) │ │ Large, slower, manual cleanup│ │ [Zombie* z] │ ││ │ [Brain* b] ← new Brain() │ ││ └──────────────────────────────┘ ││ ││ ↓ gap ↓ ││ ││ ┌──────────────────────────────┐ ││ │ STACK │ │ ← Automatic allocation│ │ (grows upward) │ │ Fast, small, automatic│ │ │ ││ │ ┌────────────────────┐ │ ││ │ │ main() │ │ ││ │ │ int x = 42; │ │ ││ │ │ Zombie z; │ ← constructor called│ │ │ │ │ ││ │ └────────────────────┘ │ ││ └──────────────────────────────┘ ││ ││ ┌──────────────────────────────┐ ││ │ DATA SEGMENT │ │ ← Global/static variables│ └──────────────────────────────┘ ││ ││ ┌──────────────────────────────┐ ││ │ CODE SEGMENT │ │ ← Program instructions│ └──────────────────────────────┘ ││ ││ LOW MEMORY │└─────────────────────────────────────┘Stack Frame Layout
Section titled “Stack Frame Layout”┌─────────────────────────────────────┐│ Stack Frame for function() │├─────────────────────────────────────┤│ Return address │ ← Where to go back to├─────────────────────────────────────┤│ Saved registers │ ← Previous state├─────────────────────────────────────┤│ Local variables: ││ int x = 42; → [ 42 ] ││ Zombie z; → [ object ] │├─────────────────────────────────────┤│ Function parameters │└─────────────────────────────────────┘ ↑ Stack grows UPObject in Memory
Section titled “Object in Memory”┌─────────────────────────────────────┐│ Contact object on STACK │├─────────────────────────────────────┤│ vptr (hidden) → vtable │ (only if class has virtual functions)├─────────────────────────────────────┤│ std::string _firstName ││ ├─ pointer to heap memory ││ └─ size/capacity info │├─────────────────────────────────────┤│ std::string _lastName │├─────────────────────────────────────┤│ int _age = 25; → [ 25 ] │└─────────────────────────────────────┘1. Stack vs Heap Memory
Section titled “1. Stack vs Heap Memory”Stack Allocation (Automatic)
Section titled “Stack Allocation (Automatic)”void function() { int x = 42; // On stack Zombie zombie; // On stack} // Automatically destroyed hereCharacteristics:
- Fast allocation/deallocation
- Limited size (~1-8 MB typically)
- Automatic cleanup when scope ends
- Cannot outlive function
Heap Allocation (Dynamic)
Section titled “Heap Allocation (Dynamic)”void function() { int* x = new int(42); // On heap Zombie* zombie = new Zombie(); // On heap
// ... use them ...
delete x; // Must manually delete delete zombie; // Must manually delete}Characteristics:
- Slower allocation
- Large capacity (limited by RAM)
- Must be manually freed
- Can outlive function (return pointer)
2. new and delete Operators
Section titled “2. new and delete Operators”Single Object
Section titled “Single Object”// Allocationint* ptr = new int; // Uninitializedint* ptr = new int(); // Zero-initializedint* ptr = new int(42); // Initialized to 42
// Deallocationdelete ptr;ptr = NULL; // Good practice (C++98)Arrays
Section titled “Arrays”// Allocationint* arr = new int[10]; // Array of 10 intsZombie* horde = new Zombie[5]; // Array of 5 Zombies
// Deallocation - NOTE THE []delete[] arr; // MUST use delete[] for arraysdelete[] horde;Common Mistakes
Section titled “Common Mistakes”// WRONG: Using delete on arrayint* arr = new int[10];delete arr; // UNDEFINED BEHAVIOR!
// WRONG: Using delete[] on single objectint* ptr = new int(42);delete[] ptr; // UNDEFINED BEHAVIOR!
// WRONG: Double deleteint* ptr = new int(42);delete ptr;delete ptr; // CRASH or corruption!When to Use Stack vs Heap
Section titled “When to Use Stack vs Heap”| Use Stack When… | Use Heap When… |
|---|---|
| Object’s lifetime = function scope | Object must outlive function |
| Size known at compile time | Size determined at runtime |
| Small objects | Large objects |
| Performance critical | Returning new objects |
3. References
Section titled “3. References”What is a Reference?
Section titled “What is a Reference?”A reference is an alias - another name for an existing variable.
int x = 42;int& ref = x; // ref IS x (not a copy, not a pointer)
ref = 100; // Changes x to 100std::cout << x; // Prints 100References vs Pointers
Section titled “References vs Pointers”| Feature | Reference | Pointer |
|---|---|---|
| Syntax | int& ref = x; | int* ptr = &x; |
| Can be null | No | Yes |
| Can be reassigned | No | Yes |
| Must be initialized | Yes | No |
| Access value | ref | *ptr |
| Access address | &ref | ptr |
Key Rules for References
Section titled “Key Rules for References”// MUST initializeint& ref; // ERROR: references must be initializedint& ref = x; // OK
// CANNOT be nullint& ref = NULL; // ERROR: cannot bind to null
// CANNOT be reassignedint& ref = x;ref = y; // This doesn't reassign ref, it copies y to x!Why References Exist
Section titled “Why References Exist”// BAD: Passing by value (copies entire object)void printZombie(Zombie z) { // z is a COPY - expensive for large objects}
// BETTER: Passing by pointervoid printZombie(Zombie* z) { if (z != NULL) // Must check for null std::cout << z->getName();}
// BEST: Passing by referencevoid printZombie(Zombie& z) { // z is the original object - no copy // Cannot be null - no check needed std::cout << z.getName();}
// CONST reference - read-only accessvoid printZombie(const Zombie& z) { std::cout << z.getName(); // Can read // z.setName("X"); // ERROR: z is const}When to Use What
Section titled “When to Use What”| Situation | Use |
|---|---|
| Never null, won’t change what it refers to | Reference |
| Might be null | Pointer |
| Needs to be reassigned | Pointer |
| Return new object from function | Pointer (or smart pointer in modern C++) |
| Optional parameter | Pointer (can pass NULL) |
4. Memory Address Demonstration (ex02)
Section titled “4. Memory Address Demonstration (ex02)”std::string str = "HI THIS IS BRAIN";std::string* stringPTR = &str; // Pointer TO strstd::string& stringREF = str; // Reference (alias) OF str
// Addresses - all should be the same!std::cout << &str << std::endl; // Address of strstd::cout << stringPTR << std::endl; // Pointer holds same addressstd::cout << &stringREF << std::endl; // Address of ref = address of str
// Values - all should be the same!std::cout << str << std::endl; // Direct accessstd::cout << *stringPTR << std::endl; // Dereference pointerstd::cout << stringREF << std::endl; // Reference is same as original5. Reference vs Pointer in Classes (ex03)
Section titled “5. Reference vs Pointer in Classes (ex03)”Using a Reference (HumanA)
Section titled “Using a Reference (HumanA)”class HumanA {private: std::string _name; Weapon& _weapon; // Reference - MUST always have a weapon
public: // MUST initialize reference in constructor HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {}
void attack() { std::cout << _name << " attacks with " << _weapon.getType(); }};
// UsageWeapon club("club");HumanA bob("Bob", club); // MUST provide weapon at constructionUsing a Pointer (HumanB)
Section titled “Using a Pointer (HumanB)”class HumanB {private: std::string _name; Weapon* _weapon; // Pointer - might not have a weapon
public: HumanB(std::string name) : _name(name), _weapon(NULL) {}
void setWeapon(Weapon& weapon) { _weapon = &weapon; }
void attack() { if (_weapon) std::cout << _name << " attacks with " << _weapon->getType(); else std::cout << _name << " has no weapon"; }};
// UsageHumanB jim("Jim"); // No weapon initiallyjim.setWeapon(club); // Weapon added laterDecision Guide
Section titled “Decision Guide”- Reference: When object MUST exist at construction and throughout lifetime
- Pointer: When object might not exist, or might change
6. Pointers to Members (ex05)
Section titled “6. Pointers to Members (ex05)”Function Pointers (C-style)
Section titled “Function Pointers (C-style)”void sayHello() { std::cout << "Hello"; }void sayBye() { std::cout << "Bye"; }
// Function pointervoid (*funcPtr)() = &sayHello;funcPtr(); // Calls sayHello()
funcPtr = &sayBye;funcPtr(); // Calls sayBye()Pointers to Member Functions
Section titled “Pointers to Member Functions”class Harl {public: void debug() { std::cout << "Debug"; } void info() { std::cout << "Info"; } void warning() { std::cout << "Warning"; } void error() { std::cout << "Error"; }
void complain(std::string level) { // Array of member function pointers 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])(); // Call the member function return; } } }};Syntax Breakdown
Section titled “Syntax Breakdown”// Declarationvoid (ClassName::*pointerName)(parameters);
// AssignmentpointerName = &ClassName::methodName;
// Call via object(object.*pointerName)(args);
// Call via pointer to object(objectPtr->*pointerName)(args);7. switch Statement (ex06)
Section titled “7. switch Statement (ex06)”Basic Syntax
Section titled “Basic Syntax”switch (expression) { case value1: // code break; case value2: // code break; default: // code if no match}Fall-through Behavior
Section titled “Fall-through Behavior”// WITHOUT break - falls through to next caseswitch (level) { case 3: // WARNING std::cout << "Warning message\n"; // No break - falls through! case 2: // INFO std::cout << "Info message\n"; // No break - falls through! case 1: // DEBUG std::cout << "Debug message\n"; break;}
// If level = 3: prints Warning, Info, Debug// If level = 2: prints Info, Debug// If level = 1: prints Debugswitch vs if-else
Section titled “switch vs if-else”// Can only switch on integral types in C++98switch (number) { ... } // OKswitch (character) { ... } // OKswitch (string) { ... } // ERROR in C++98!
// For strings, must use if-elseif (str == "DEBUG") { ... }else if (str == "INFO") { ... }String-to-Integer Conversion for Switch
Section titled “String-to-Integer Conversion for Switch”Since C++98 cannot switch on strings, convert strings to integers first:
// Convert string to index for switchint 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; // Not found}
void complain(const std::string& level) { switch (getLevel(level)) { case 0: std::cout << "Debug message" << std::endl; break; case 1: std::cout << "Info message" << std::endl; break; case 2: std::cout << "Warning message" << std::endl; break; case 3: std::cout << "Error message" << std::endl; break; default: std::cout << "Unknown level" << std::endl; }}This is cleaner than a long if-else chain and allows fall-through behavior.
8. File I/O (ex04)
Section titled “8. File I/O (ex04)”Reading from File
Section titled “Reading from File”#include <fstream>#include <string>
std::ifstream inFile("input.txt");
if (!inFile.is_open()) { std::cerr << "Cannot open file" << std::endl; return 1;}
std::string line;while (std::getline(inFile, line)) { std::cout << line << std::endl;}
inFile.close();Writing to File
Section titled “Writing to File”std::ofstream outFile("output.txt");
if (!outFile.is_open()) { std::cerr << "Cannot create file" << std::endl; return 1;}
outFile << "Hello, World!" << std::endl;outFile << "Line 2" << std::endl;
outFile.close();Reading Entire File into String
Section titled “Reading Entire File into String”std::ifstream inFile("input.txt");std::string content;std::string line;
while (std::getline(inFile, line)) { content += line; content += "\n";}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
- Subject requirement: avoid a “forest” of
if/else if/else(use pointers to member functions)
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;}Quick Reference
Section titled “Quick Reference”Memory
Section titled “Memory”// Single objectType* ptr = new Type(args);delete ptr;
// ArrayType* arr = new Type[size];delete[] arr;References
Section titled “References”Type& ref = original; // Create alias// ref is now indistinguishable from originalMember Function Pointers
Section titled “Member Function Pointers”void (Class::*ptr)(args) = &Class::method;(object.*ptr)(args);File I/O
Section titled “File I/O”std::ifstream in("file");std::ofstream out("file");std::getline(in, str);out << content;Related Concepts
Section titled “Related Concepts”Continue your C++ journey:
- Previous: Module 00: C++ Fundamentals - Review classes and basic syntax
- Next: Module 02: OCF & Operators - Learn the Orthodox Canonical Form and why proper copy semantics matter
Key Terms from This Module
Section titled “Key Terms from This Module”Visit the Glossary for definitions of:
- Stack and Heap
- Pointer and Reference
- Memory Leak