Skip to content

Module 00 Tutorial

Prerequisites: Review Module 00 Concepts first.

The Megaphone exercise asks you to create a program that:

  • Takes command-line arguments and converts them all to uppercase
  • Concatenates all arguments without adding spaces between them
  • Prints a default message * LOUD AND UNBEARABLE FEEDBACK NOISE * when no arguments are given

This is your first C++ program. The goal is to learn basic I/O with std::cout and understand how command-line arguments work in C++.

Before writing any code, think through the problem:

  1. Understand argc/argv: argc is the argument count, argv is an array of C-strings. argv[0] is always the program name.
  2. Handle the edge case first: Check if no arguments were given (argc == 1).
  3. Process each argument: Loop from index 1 to skip the program name.
  4. Convert character by character: For each argument, loop through each character and convert to uppercase.
  5. Output at the end: Print a single newline after all characters.

Stage 1 - Basic skeleton:

#include <iostream>
int main(int argc, char **argv)
{
// TODO: implement
return 0;
}

Stage 2 - Handle no-args case:

#include <iostream>
int main(int argc, char **argv)
{
if (argc == 1)
{
std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl;
return 0;
}
// TODO: handle arguments
return 0;
}

Stage 3 - Complete implementation:

#include <iostream>
#include <cctype>
int main(int argc, char **argv)
{
if (argc == 1)
{
std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl;
return 0;
}
for (int i = 1; i < argc; i++)
{
for (int j = 0; argv[i][j]; j++)
{
std::cout << (char)toupper(argv[i][j]);
}
}
std::cout << std::endl;
return 0;
}
LineCodeWhy
1#include <iostream>C++ I/O library. Use this instead of stdio.h. Gives us std::cout.
2#include <cctype>C++ wrapper for character functions. Provides toupper().
4int main(int argc, char **argv)Standard signature for programs receiving command-line arguments.
6if (argc == 1)Why 1, not 0? Because argv[0] is always the program name, so 1 means “no actual arguments”.
8std::cout << ... << std::endl;Stream the message to stdout. std::endl flushes the buffer and adds newline.
9return 0;Exit early after printing the default message.
12for (int i = 1; i < argc; i++)Start at 1 to skip argv[0] (the program name).
14for (int j = 0; argv[i][j]; j++)Loop until null terminator. argv[i][j] is the j-th character of i-th argument.
16(char)toupper(argv[i][j])toupper() returns an int, so we cast to char for proper output.
19std::cout << std::endl;Print final newline only after all characters are output.
  1. Using printf instead of std::cout

    • C habit! In C++ modules, you must use std::cout for output.
    • Wrong: printf("hello");
    • Right: std::cout << "hello";
  2. Forgetting that toupper() returns int

    • Without the cast, you may print ASCII codes instead of characters.
    • Wrong: std::cout << toupper(c);
    • Right: std::cout << (char)toupper(c);
  3. Forgetting the no-argument case

    • The subject explicitly requires printing * LOUD AND UNBEARABLE FEEDBACK NOISE * when there are no arguments.
    • Always test with ./megaphone (no args).
  4. Adding spaces between arguments

    • The subject wants arguments concatenated directly.
    • ./megaphone "Hello" "World" should output HELLOWORLD, not HELLO WORLD.

Test your program with the exact examples from the subject:

Terminal window
./megaphone "shhhhh... I think the students are asleep..."
# Expected: SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
./megaphone Damnit " ! " "Sorry students, I thought this thing was off."
# Expected: DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
./megaphone
# Expected: * LOUD AND UNBEARABLE FEEDBACK NOISE *

Additional edge cases to verify:

Terminal window
./megaphone ""
# Expected: (empty line - just a newline)
./megaphone "123" "!@#"
# Expected: 123!@# (non-letters pass through unchanged)
megaphone.cpp
#include <iostream>
#include <cctype>
int main(int argc, char **argv)
{
if (argc == 1)
{
std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl;
return 0;
}
for (int i = 1; i < argc; i++)
{
for (int j = 0; argv[i][j]; j++)
{
std::cout << (char)toupper(argv[i][j]);
}
}
std::cout << std::endl;
return 0;
}

Your first real C++ program with classes. This exercise teaches you object-oriented fundamentals: encapsulation, class design, and how objects interact.

The PhoneBook exercise requires you to build a contact management system with these constraints:

Two classes needed:

  • Contact: Stores data for a single person (first name, last name, nickname, phone number, darkest secret)
  • PhoneBook: Manages a collection of up to 8 contacts

Core requirements:

  • Fixed array of 8 contacts - NO vectors, NO dynamic allocation (new/delete)
  • Three commands: ADD, SEARCH, EXIT
  • Circular buffer: When full, oldest contact gets replaced
  • No empty fields: Every field must have content
  • SEARCH display: 10-character columns, right-aligned, truncate with ”.” if too long
  • EOF handling: Program must exit gracefully on Ctrl+D

The SEARCH command has two phases:

  1. Display a formatted table showing all contacts
  2. Prompt for an index, then display that contact’s full details

Think through the design before coding:

Step 1 - Design the Contact class first

Ask yourself: What data does a contact hold? What methods does it need?

  • 5 private string attributes (the fields)
  • A flag to know if the slot is used (_isEmpty)
  • Setters to store data, getters to retrieve it
  • Everything private except the interface methods

Step 2 - Design the PhoneBook class

Ask yourself: How do I manage 8 contacts? How do I know where to add next?

  • Fixed array: Contact _contacts[8]
  • Track where to add next: _currentIndex (cycles 0→7→0)
  • Track how many exist: _totalContacts (caps at 8)

Step 3 - Plan the main loop

while (true) {
read command
if ADD → call phonebook.addContact()
if SEARCH → call phonebook.searchContacts()
if EXIT → break
}

Step 4 - Handle edge cases

  • Empty input rejection (loop until non-empty)
  • EOF handling (std::getline returns false)
  • Invalid index (check range and format)

Key insight - The circular buffer:

You need two trackers:

  • _currentIndex: Where to add the NEXT contact (wraps with modulo 8)
  • _totalContacts: How many contacts exist (maximum 8)

When adding contact #9, _currentIndex will be 0, replacing the oldest contact.

Stage 1 - Contact Class (the data container):

Contact.hpp
#ifndef CONTACT_HPP
#define CONTACT_HPP
#include <string>
class Contact {
private:
std::string _firstName;
std::string _lastName;
std::string _nickname;
std::string _phoneNumber;
std::string _darkestSecret;
bool _isEmpty;
public:
Contact();
~Contact();
// Setters - take const reference for efficiency
void setFirstName(const std::string& firstName);
void setLastName(const std::string& lastName);
void setNickname(const std::string& nickname);
void setPhoneNumber(const std::string& phoneNumber);
void setDarkestSecret(const std::string& secret);
void setIsEmpty(bool isEmpty);
// Getters - marked const (promise not to modify object)
std::string getFirstName() const;
std::string getLastName() const;
std::string getNickname() const;
std::string getPhoneNumber() const;
std::string getDarkestSecret() const;
bool isEmpty() const;
};
#endif

Why these design decisions?

DecisionReason
Private attributesEncapsulation - external code can’t corrupt data
const std::string& paramsAvoids copying the string, more efficient
const on gettersPromises these methods won’t modify the object
_isEmpty flagLets us check if a contact slot is actually used
Underscore prefixCommon convention to identify private members

Stage 2 - PhoneBook Class (the manager):

PhoneBook.hpp
#ifndef PHONEBOOK_HPP
#define PHONEBOOK_HPP
#include "Contact.hpp"
#include <string>
class PhoneBook {
private:
Contact _contacts[8]; // Fixed array - NO vectors!
int _currentIndex; // Where to add next (0-7, wraps)
int _totalContacts; // How many stored (max 8)
// Private helper methods - internal implementation details
std::string _truncate(const std::string& str) const;
void _displayContactRow(int index) const;
std::string _getInput(const std::string& prompt) const;
public:
PhoneBook();
~PhoneBook();
void addContact();
void searchContacts() const;
};
#endif

Why Contact _contacts[8] and not a vector?

The subject explicitly forbids containers from the STL. This forces you to understand how fixed-size arrays work in C++. The array is allocated on the stack as part of the PhoneBook object itself.

Stage 3 - The Circular Buffer Logic:

void PhoneBook::addContact() {
Contact newContact;
// Collect all fields (helper rejects empty input)
newContact.setFirstName(_getInput("Enter first name: "));
newContact.setLastName(_getInput("Enter last name: "));
newContact.setNickname(_getInput("Enter nickname: "));
newContact.setPhoneNumber(_getInput("Enter phone number: "));
newContact.setDarkestSecret(_getInput("Enter darkest secret: "));
newContact.setIsEmpty(false);
// Store at current index (overwrites oldest if full)
_contacts[_currentIndex] = newContact;
// Circular buffer: wrap around using modulo
_currentIndex = (_currentIndex + 1) % 8;
// Track count (cap at 8)
if (_totalContacts < 8)
_totalContacts++;
std::cout << "Contact added successfully!" << std::endl;
}

The modulo trick explained:

(_currentIndex + 1) % 8 makes the index cycle: 0→1→2→3→4→5→6→7→0→1→2…

_currentIndex+ 1% 8Result
011 % 81
677 % 87
788 % 80 ← wraps!

Stage 4 - SEARCH Display Formatting:

std::string PhoneBook::_truncate(const std::string& str) const {
if (str.length() > 10)
return str.substr(0, 9) + "."; // 9 chars + dot = 10
return str;
}
void PhoneBook::_displayContactRow(int index) const {
std::cout << std::setw(10) << std::right << index << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;
}

Understanding std::setw and std::right:

  • std::setw(10) - Set width to 10 characters for the next output
  • std::right - Align content to the right within that width

Example: "John" becomes " John" (6 spaces + 4 chars = 10)

Input validation with EOF handling:

std::string PhoneBook::_getInput(const std::string& prompt) const {
std::string input;
while (true) {
std::cout << prompt;
if (!std::getline(std::cin, input)) { // Returns false on EOF
std::cout << std::endl;
std::exit(0); // Clean exit on Ctrl+D
}
if (!input.empty())
break;
std::cout << "Field cannot be empty. Please try again." << std::endl;
}
return input;
}
LineWhy
std::getline(std::cin, input)Reads entire line including spaces (unlike std::cin >>)
if (!std::getline(...))getline returns false when EOF (Ctrl+D) is encountered
std::exit(0)Terminates program cleanly - required for EOF handling
if (!input.empty())Rejects empty strings, keeps prompting

Index validation for SEARCH:

// Get and validate index
std::string indexStr;
std::cout << "Enter index to view details: ";
if (!std::getline(std::cin, indexStr)) {
std::cout << std::endl;
return;
}
// Strict validation: must be single digit 0-7
if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') {
std::cout << "Invalid index." << std::endl;
return;
}
int index = indexStr[0] - '0'; // Convert char digit to int
if (index >= _totalContacts) {
std::cout << "Invalid index." << std::endl;
return;
}
LineWhy
indexStr.length() != 1Only accept single character input
indexStr[0] < '0' || indexStr[0] > '7'Must be digit 0-7
indexStr[0] - '0'Character to int conversion: '5' - '0' = 5
index >= _totalContactsCan’t access contact that doesn’t exist

1. Using vectors or dynamic allocation

The subject EXPLICITLY forbids this. You will fail the evaluation.

// WRONG - forbidden
std::vector<Contact> _contacts;
Contact* _contacts = new Contact[8];
// RIGHT - fixed array
Contact _contacts[8];

2. Wrong display formatting

// WRONG - no formatting
std::cout << firstName << "|" << lastName << std::endl;
// RIGHT - 10 chars, right-aligned, truncated
std::cout << std::setw(10) << std::right;
if (firstName.length() > 10)
std::cout << firstName.substr(0, 9) + ".";
else
std::cout << firstName;

3. Not handling the circular buffer correctly

// WRONG - just keeps incrementing
_currentIndex++;
// RIGHT - wraps around
_currentIndex = (_currentIndex + 1) % 8;

4. Accepting empty fields

// WRONG - accepts anything
std::getline(std::cin, input);
contact.setFirstName(input);
// RIGHT - loop until non-empty
while (input.empty()) {
std::cout << "Field cannot be empty: ";
std::getline(std::cin, input);
}

5. Not handling EOF (Ctrl+D)

// WRONG - ignores EOF, program hangs
std::getline(std::cin, input);
// RIGHT - check return value and exit
if (!std::getline(std::cin, input)) {
std::cout << std::endl;
std::exit(0);
}

6. Making attributes public

// WRONG - breaks encapsulation
class Contact {
public:
std::string firstName; // Anyone can modify directly!
};
// RIGHT - private with accessors
class Contact {
private:
std::string _firstName;
public:
void setFirstName(const std::string& name);
std::string getFirstName() const;
};
Terminal window
# Test ADD functionality
> ADD
Enter first name: John
Enter last name: Doe
Enter nickname: JD
Enter phone number: 555-1234
Enter darkest secret: Likes pineapple pizza
Contact added successfully!
# Test empty field rejection
> ADD
Enter first name:
Field cannot be empty. Please try again.
Enter first name: Jane
# Test SEARCH with no contacts
> SEARCH
PhoneBook is empty.
# Test SEARCH display formatting
> SEARCH
Index|First Name| Last Name| Nickname
--------------------------------------------
0| John| Doe| JD
Enter index to view details: 0
First Name: John
Last Name: Doe
Nickname: JD
Phone Number: 555-1234
Darkest Secret: Likes pineapple pizza
# Test truncation (name > 10 chars)
# Add contact with first name "Christopher"
> SEARCH
# Should display "Christoph." (9 chars + dot)
# Test circular buffer (add 9 contacts)
# Contact #9 should replace contact #0
# Test invalid index
> SEARCH
Enter index to view details: 9
Invalid index.
> SEARCH
Enter index to view details: abc
Invalid index.
# Test EOF handling
> (Press Ctrl+D)
# Program should exit cleanly with newline

Contact.hpp

Contact.hpp
#ifndef CONTACT_HPP
#define CONTACT_HPP
#include <string>
class Contact {
private:
std::string _firstName;
std::string _lastName;
std::string _nickname;
std::string _phoneNumber;
std::string _darkestSecret;
bool _isEmpty;
public:
Contact();
~Contact();
void setFirstName(const std::string& firstName);
void setLastName(const std::string& lastName);
void setNickname(const std::string& nickname);
void setPhoneNumber(const std::string& phoneNumber);
void setDarkestSecret(const std::string& secret);
void setIsEmpty(bool isEmpty);
std::string getFirstName() const;
std::string getLastName() const;
std::string getNickname() const;
std::string getPhoneNumber() const;
std::string getDarkestSecret() const;
bool isEmpty() const;
};
#endif

Contact.cpp

Contact.cpp
#include "Contact.hpp"
Contact::Contact() : _isEmpty(true) {}
Contact::~Contact() {}
void Contact::setFirstName(const std::string& firstName) { _firstName = firstName; }
void Contact::setLastName(const std::string& lastName) { _lastName = lastName; }
void Contact::setNickname(const std::string& nickname) { _nickname = nickname; }
void Contact::setPhoneNumber(const std::string& phoneNumber) { _phoneNumber = phoneNumber; }
void Contact::setDarkestSecret(const std::string& secret) { _darkestSecret = secret; }
void Contact::setIsEmpty(bool isEmpty) { _isEmpty = isEmpty; }
std::string Contact::getFirstName() const { return _firstName; }
std::string Contact::getLastName() const { return _lastName; }
std::string Contact::getNickname() const { return _nickname; }
std::string Contact::getPhoneNumber() const { return _phoneNumber; }
std::string Contact::getDarkestSecret() const { return _darkestSecret; }
bool Contact::isEmpty() const { return _isEmpty; }

PhoneBook.hpp

PhoneBook.hpp
#ifndef PHONEBOOK_HPP
#define PHONEBOOK_HPP
#include "Contact.hpp"
#include <string>
class PhoneBook {
private:
Contact _contacts[8];
int _currentIndex;
int _totalContacts;
std::string _truncate(const std::string& str) const;
void _displayContactRow(int index) const;
std::string _getInput(const std::string& prompt) const;
public:
PhoneBook();
~PhoneBook();
void addContact();
void searchContacts() const;
};
#endif

PhoneBook.cpp

PhoneBook.cpp
#include "PhoneBook.hpp"
#include <iostream>
#include <iomanip>
#include <cstdlib>
PhoneBook::PhoneBook() : _currentIndex(0), _totalContacts(0) {}
PhoneBook::~PhoneBook() {}
std::string PhoneBook::_truncate(const std::string& str) const {
if (str.length() > 10)
return str.substr(0, 9) + ".";
return str;
}
void PhoneBook::_displayContactRow(int index) const {
std::cout << std::setw(10) << std::right << index << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|";
std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;
}
std::string PhoneBook::_getInput(const std::string& prompt) const {
std::string input;
while (true) {
std::cout << prompt;
if (!std::getline(std::cin, input)) {
std::cout << std::endl;
std::exit(0);
}
if (!input.empty())
break;
std::cout << "Field cannot be empty. Please try again." << std::endl;
}
return input;
}
void PhoneBook::addContact() {
Contact newContact;
newContact.setFirstName(_getInput("Enter first name: "));
newContact.setLastName(_getInput("Enter last name: "));
newContact.setNickname(_getInput("Enter nickname: "));
newContact.setPhoneNumber(_getInput("Enter phone number: "));
newContact.setDarkestSecret(_getInput("Enter darkest secret: "));
newContact.setIsEmpty(false);
_contacts[_currentIndex] = newContact;
_currentIndex = (_currentIndex + 1) % 8;
if (_totalContacts < 8)
_totalContacts++;
std::cout << "Contact added successfully!" << std::endl;
}
void PhoneBook::searchContacts() const {
if (_totalContacts == 0) {
std::cout << "PhoneBook is empty." << std::endl;
return;
}
// Display header
std::cout << std::setw(10) << "Index" << "|";
std::cout << std::setw(10) << "First Name" << "|";
std::cout << std::setw(10) << "Last Name" << "|";
std::cout << std::setw(10) << "Nickname" << std::endl;
std::cout << std::string(44, '-') << std::endl;
// Display all contacts
for (int i = 0; i < _totalContacts; i++)
_displayContactRow(i);
// Get index from user
std::cout << "Enter index to view details: ";
std::string indexStr;
if (!std::getline(std::cin, indexStr)) {
std::cout << std::endl;
return;
}
// Validate index
if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') {
std::cout << "Invalid index." << std::endl;
return;
}
int index = indexStr[0] - '0';
if (index >= _totalContacts) {
std::cout << "Invalid index." << std::endl;
return;
}
// Display full contact details
std::cout << "First Name: " << _contacts[index].getFirstName() << std::endl;
std::cout << "Last Name: " << _contacts[index].getLastName() << std::endl;
std::cout << "Nickname: " << _contacts[index].getNickname() << std::endl;
std::cout << "Phone Number: " << _contacts[index].getPhoneNumber() << std::endl;
std::cout << "Darkest Secret: " << _contacts[index].getDarkestSecret() << std::endl;
}

main.cpp

main.cpp
#include "PhoneBook.hpp"
#include <iostream>
#include <cstdlib>
int main() {
PhoneBook phonebook;
std::string command;
while (true) {
std::cout << "Enter command (ADD, SEARCH, EXIT): ";
if (!std::getline(std::cin, command)) {
std::cout << std::endl;
break;
}
if (command == "ADD")
phonebook.addContact();
else if (command == "SEARCH")
phonebook.searchContacts();
else if (command == "EXIT")
break;
}
return 0;
}

Exercise 02: The Job Of Your Dreams (Bonus)

Section titled “Exercise 02: The Job Of Your Dreams (Bonus)”

Note: This is a BONUS exercise. Complete it after ex00 and ex01 if you want extra practice with static members and reverse-engineering.

This exercise is different. Instead of building from scratch, you’re given pieces and must deduce the missing parts. You receive:

  • Account.hpp - The complete class declaration
  • tests.cpp - Test code that uses the class
  • A log file - The expected output showing what should happen

Your job: Write Account.cpp to make the tests produce output matching the log (except timestamps).

What you’re given:

The header file Account.hpp declares:

  • Static members tracking global state (_nbAccounts, _totalAmount, etc.)
  • Instance members for each account (_accountIndex, _amount, etc.)
  • A private _displayTimestamp() method
  • Constructor, destructor, and various methods

What you must figure out:

  1. How to initialize static members (they MUST be defined somewhere)
  2. How to format the timestamp from the log: [YYYYMMDD_HHMMSS]
  3. The exact output format (semicolon-separated key:value pairs)
  4. What each method should print and when

Step 1 - Analyze the header file

Read every line of Account.hpp. List what you need to implement:

  • 4 static members need initialization
  • 4 static getter methods
  • 1 static display method (displayAccountsInfos)
  • Constructor and destructor
  • makeDeposit, makeWithdrawal, checkAmount, displayStatus
  • Private _displayTimestamp

Step 2 - Study the log file

The log shows EXACTLY what output to produce. Key observations:

  • Every line starts with a timestamp: [19920104_091532]
  • Format is semicolon-separated: index:0;amount:42;created
  • Destructor calls happen in REVERSE order of construction

Step 3 - Map log lines to methods

Log PatternMethod
accounts:N;total:N;deposits:N;withdrawals:NdisplayAccountsInfos()
index:N;amount:N;createdConstructor
index:N;amount:N;closedDestructor
index:N;p_amount:N;deposit:N;amount:N;nb_deposits:NmakeDeposit()
index:N;p_amount:N;withdrawal:refusedmakeWithdrawal() (fail)
index:N;p_amount:N;withdrawal:N;amount:N;nb_withdrawals:NmakeWithdrawal() (success)

Stage 1 - Static member initialization:

#include "Account.hpp"
// CRITICAL: Static members MUST be initialized outside the class
// This allocates actual storage for them
int Account::_nbAccounts = 0;
int Account::_totalAmount = 0;
int Account::_totalNbDeposits = 0;
int Account::_totalNbWithdrawals = 0;

Why outside the class? Static members are shared by ALL instances. The header only declares them; you must define (allocate storage for) them exactly once in a .cpp file.

Stage 2 - Timestamp formatting:

#include <ctime>
#include <iomanip>
void Account::_displayTimestamp() {
std::time_t now = std::time(NULL);
std::tm* local = std::localtime(&now);
std::cout << "["
<< (local->tm_year + 1900) // Year since 1900
<< std::setfill('0') << std::setw(2) << (local->tm_mon + 1) // Month 0-11
<< std::setfill('0') << std::setw(2) << local->tm_mday
<< "_"
<< std::setfill('0') << std::setw(2) << local->tm_hour
<< std::setfill('0') << std::setw(2) << local->tm_min
<< std::setfill('0') << std::setw(2) << local->tm_sec
<< "] ";
}

Understanding <ctime>:

FunctionPurpose
std::time(NULL)Get current time as seconds since epoch
std::localtime(&now)Convert to local time struct (tm)
tm->tm_yearYears since 1900 (add 1900 for actual year)
tm->tm_monMonth 0-11 (add 1 for human-readable)

Stage 3 - Constructor and destructor:

Account::Account(int initial_deposit)
: _accountIndex(_nbAccounts), // Capture current count BEFORE increment
_amount(initial_deposit),
_nbDeposits(0),
_nbWithdrawals(0) {
_nbAccounts++; // NOW increment for next account
_totalAmount += initial_deposit;
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";amount:" << _amount
<< ";created" << std::endl;
}
Account::~Account() {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";amount:" << _amount
<< ";closed" << std::endl;
}

Why does destructor order matter?

C++ destroys objects in REVERSE order of construction. For arrays:

  • First created (index 0) → last destroyed
  • Last created (index 7) → first destroyed

The log file shows this clearly - check that your destructor output matches.

The withdrawal logic:

bool Account::makeWithdrawal(int withdrawal) {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";p_amount:" << _amount // Previous amount (before)
<< ";withdrawal:";
if (withdrawal > _amount) {
std::cout << "refused" << std::endl; // Can't withdraw more than balance
return false;
}
_amount -= withdrawal;
_nbWithdrawals++;
_totalAmount -= withdrawal; // Update global tracker
_totalNbWithdrawals++;
std::cout << withdrawal
<< ";amount:" << _amount // New amount (after)
<< ";nb_withdrawals:" << _nbWithdrawals << std::endl;
return true;
}
LineWhy
p_amount output firstLog shows “previous amount” before withdrawal attempt
Check withdrawal > _amountCan’t go negative - refuse if insufficient funds
Update both instance AND static_amount is this account, _totalAmount is all accounts
Return boolTests may check success/failure

1. Initializing static members in the header

// WRONG - in Account.hpp
class Account {
private:
static int _nbAccounts = 0; // ERROR: can't initialize here (pre-C++17)
};
// RIGHT - in Account.cpp
int Account::_nbAccounts = 0; // Definition with initialization

2. Forgetting to add 1900/1 to time values

// WRONG
std::cout << local->tm_year; // Prints "125" for year 2025!
std::cout << local->tm_mon; // Prints "0" for January!
// RIGHT
std::cout << (local->tm_year + 1900); // Prints "2025"
std::cout << (local->tm_mon + 1); // Prints "1"

3. Wrong output format

// WRONG - spaces and different separators
std::cout << "index: " << _accountIndex << ", amount: " << _amount;
// RIGHT - match the log exactly
std::cout << "index:" << _accountIndex << ";amount:" << _amount;

4. Not understanding destructor order

When you create accounts 0, 1, 2… they’re destroyed 2, 1, 0. The log file confirms this - verify your output matches.

Since you have a reference log file, testing is straightforward:

Terminal window
# Compile your implementation
c++ -Wall -Wextra -Werror Account.cpp tests.cpp -o account_test
# Run and capture output
./account_test > my_output.txt
# Compare with reference (ignoring timestamps)
# The timestamps will differ, but everything else should match
# Strip timestamps for comparison
sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' my_output.txt > my_clean.txt
sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' reference.log > ref_clean.txt
diff my_clean.txt ref_clean.txt

What to verify:

  1. Account creation messages appear in order (0, 1, 2…)
  2. Account destruction messages appear in REVERSE order
  3. Withdrawal refusals say exactly “refused”
  4. All semicolons and colons are in the right places
Account.cpp
#include "Account.hpp"
#include <iostream>
#include <iomanip>
#include <ctime>
// Initialize static members
int Account::_nbAccounts = 0;
int Account::_totalAmount = 0;
int Account::_totalNbDeposits = 0;
int Account::_totalNbWithdrawals = 0;
// Static getters
int Account::getNbAccounts() { return _nbAccounts; }
int Account::getTotalAmount() { return _totalAmount; }
int Account::getNbDeposits() { return _totalNbDeposits; }
int Account::getNbWithdrawals() { return _totalNbWithdrawals; }
// Display timestamp: [YYYYMMDD_HHMMSS]
void Account::_displayTimestamp() {
std::time_t now = std::time(NULL);
std::tm* local = std::localtime(&now);
std::cout << "["
<< (local->tm_year + 1900)
<< std::setfill('0') << std::setw(2) << (local->tm_mon + 1)
<< std::setfill('0') << std::setw(2) << local->tm_mday
<< "_"
<< std::setfill('0') << std::setw(2) << local->tm_hour
<< std::setfill('0') << std::setw(2) << local->tm_min
<< std::setfill('0') << std::setw(2) << local->tm_sec
<< "] ";
}
// Display all accounts info
void Account::displayAccountsInfos() {
_displayTimestamp();
std::cout << "accounts:" << _nbAccounts
<< ";total:" << _totalAmount
<< ";deposits:" << _totalNbDeposits
<< ";withdrawals:" << _totalNbWithdrawals << std::endl;
}
// Constructor
Account::Account(int initial_deposit)
: _accountIndex(_nbAccounts),
_amount(initial_deposit),
_nbDeposits(0),
_nbWithdrawals(0) {
_nbAccounts++;
_totalAmount += initial_deposit;
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";amount:" << _amount
<< ";created" << std::endl;
}
// Destructor
Account::~Account() {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";amount:" << _amount
<< ";closed" << std::endl;
}
// Make a deposit
void Account::makeDeposit(int deposit) {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";p_amount:" << _amount
<< ";deposit:" << deposit;
_amount += deposit;
_nbDeposits++;
_totalAmount += deposit;
_totalNbDeposits++;
std::cout << ";amount:" << _amount
<< ";nb_deposits:" << _nbDeposits << std::endl;
}
// Make a withdrawal
bool Account::makeWithdrawal(int withdrawal) {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";p_amount:" << _amount
<< ";withdrawal:";
if (withdrawal > _amount) {
std::cout << "refused" << std::endl;
return false;
}
_amount -= withdrawal;
_nbWithdrawals++;
_totalAmount -= withdrawal;
_totalNbWithdrawals++;
std::cout << withdrawal
<< ";amount:" << _amount
<< ";nb_withdrawals:" << _nbWithdrawals << std::endl;
return true;
}
// Check amount
int Account::checkAmount() const {
return _amount;
}
// Display status
void Account::displayStatus() const {
_displayTimestamp();
std::cout << "index:" << _accountIndex
<< ";amount:" << _amount
<< ";deposits:" << _nbDeposits
<< ";withdrawals:" << _nbWithdrawals << std::endl;
}