Skip to content

Module 05 Tutorial

Prerequisites: Review Module 05 Concepts first.

Module 05 introduces exceptions - C++‘s mechanism for handling error conditions. You’ll build a bureaucratic system where Bureaucrats sign and execute Forms, with proper error handling throughout.


Exercise 00: Mommy, When I Grow Up, I Want to Be a Bureaucrat!

Section titled “Exercise 00: Mommy, When I Grow Up, I Want to Be a Bureaucrat!”

Create a Bureaucrat class with:

  • Name: Constant, set at construction
  • Grade: 1 (highest) to 150 (lowest)
  • Exceptions: GradeTooHighException and GradeTooLowException

Key methods:

  • incrementGrade(): Makes grade BETTER (lower number)
  • decrementGrade(): Makes grade WORSE (higher number)
  • Both throw if grade would become invalid

Step 1 - Understand the grade system

  • Grade 1 = President (highest rank)
  • Grade 150 = Intern (lowest rank)
  • Increment = promotion = grade goes DOWN
  • Decrement = demotion = grade goes UP

Step 2 - Create nested exception classes

Exceptions are classes nested inside Bureaucrat that inherit from std::exception.

Step 3 - Throw exceptions for invalid operations

Constructor and grade changes must validate.

Stage 1 - Exception classes:

Bureaucrat.hpp
#ifndef BUREAUCRAT_HPP
#define BUREAUCRAT_HPP
#include <string>
#include <exception>
#include <iostream>
class Bureaucrat {
private:
const std::string _name;
int _grade; // 1 (best) to 150 (worst)
public:
// Nested exception classes
class GradeTooHighException : public std::exception {
public:
const char* what() const throw() {
return "Grade is too high!";
}
};
class GradeTooLowException : public std::exception {
public:
const char* what() const throw() {
return "Grade is too low!";
}
};
Bureaucrat(const std::string& name, int grade);
Bureaucrat(const Bureaucrat& other);
Bureaucrat& operator=(const Bureaucrat& other);
~Bureaucrat();
const std::string& getName() const;
int getGrade() const;
void incrementGrade(); // Grade goes DOWN (better)
void decrementGrade(); // Grade goes UP (worse)
};
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b);
#endif

Stage 2 - Constructor with validation:

Bureaucrat.cpp
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade)
{
if (grade < 1)
throw GradeTooHighException();
if (grade > 150)
throw GradeTooLowException();
}

Stage 3 - Grade modification with exceptions:

Bureaucrat.cpp
void Bureaucrat::incrementGrade() {
if (_grade - 1 < 1)
throw GradeTooHighException();
_grade--; // Grade goes DOWN = better
}
void Bureaucrat::decrementGrade() {
if (_grade + 1 > 150)
throw GradeTooLowException();
_grade++; // Grade goes UP = worse
}
LineCodeWhy
const std::string _nameConst memberName can’t change after construction
: public std::exceptionInheritAllows catching with catch (std::exception&)
const char* what() const throw()OverrideStandard exception interface
throw GradeTooHighException()Throw exceptionCreate and throw exception object
_grade--Increment = lowerGrade 1 is best, so “up” means lower number

1. Grade logic backwards

// WRONG - increment should make grade BETTER (lower)
void Bureaucrat::incrementGrade() {
_grade++; // This makes grade worse!
}
// RIGHT
void Bureaucrat::incrementGrade() {
_grade--; // Lower number = better grade
}

2. Exception class not inheriting std::exception

// WRONG - can't catch with std::exception
class GradeTooHighException { };
// RIGHT - inherits from std::exception
class GradeTooHighException : public std::exception {
const char* what() const throw() {
return "Grade is too high!";
}
};

3. Forgetting to validate in constructor

// WRONG - allows invalid grades
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade) { }
// RIGHT - validate first
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade)
{
if (grade < 1) throw GradeTooHighException();
if (grade > 150) throw GradeTooLowException();
}
int main() {
try {
Bureaucrat b1("Bob", 2);
std::cout << b1 << std::endl; // "Bob, bureaucrat grade 2"
b1.incrementGrade(); // Grade becomes 1
std::cout << b1 << std::endl; // "Bob, bureaucrat grade 1"
b1.incrementGrade(); // Should throw!
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
try {
Bureaucrat invalid("Bad", 0); // Should throw
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
Bureaucrat.cpp
#include "Bureaucrat.hpp"
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade)
{
if (grade < 1)
throw GradeTooHighException();
if (grade > 150)
throw GradeTooLowException();
}
Bureaucrat::Bureaucrat(const Bureaucrat& other)
: _name(other._name), _grade(other._grade) { }
Bureaucrat& Bureaucrat::operator=(const Bureaucrat& other) {
if (this != &other)
_grade = other._grade; // Can't change _name (const)
return *this;
}
Bureaucrat::~Bureaucrat() { }
const std::string& Bureaucrat::getName() const { return _name; }
int Bureaucrat::getGrade() const { return _grade; }
void Bureaucrat::incrementGrade() {
if (_grade - 1 < 1)
throw GradeTooHighException();
_grade--;
}
void Bureaucrat::decrementGrade() {
if (_grade + 1 > 150)
throw GradeTooLowException();
_grade++;
}
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b) {
os << b.getName() << ", bureaucrat grade " << b.getGrade();
return os;
}

Create a Form class that:

  • Has name, signed status, and two grade requirements
  • gradeToSign: Minimum grade needed to sign
  • gradeToExecute: Minimum grade needed to execute
  • Bureaucrat can signForm() - signs if grade is good enough

Step 1 - Understand grade comparison

Remember: lower grade number = higher rank

  • If Bureaucrat grade is 5 and form requires 10: OK (5 < 10)
  • If Bureaucrat grade is 15 and form requires 10: FAIL (15 > 10)

Step 2 - Add exceptions to Form

Form throws if bureaucrat’s grade is too low.

Step 3 - Implement signForm in Bureaucrat

signForm() tries to sign and reports success/failure.

Stage 1 - Form class:

Form.hpp
#ifndef FORM_HPP
#define FORM_HPP
#include <string>
#include <exception>
#include "Bureaucrat.hpp"
class Form {
private:
const std::string _name;
bool _signed;
const int _gradeToSign;
const int _gradeToExecute;
public:
class GradeTooHighException : public std::exception {
public:
const char* what() const throw() { return "Grade is too high!"; }
};
class GradeTooLowException : public std::exception {
public:
const char* what() const throw() { return "Grade is too low!"; }
};
Form(const std::string& name, int gradeToSign, int gradeToExecute);
Form(const Form& other);
Form& operator=(const Form& other);
~Form();
const std::string& getName() const;
bool isSigned() const;
int getGradeToSign() const;
int getGradeToExecute() const;
void beSigned(const Bureaucrat& b); // Throws if grade too low
};
std::ostream& operator<<(std::ostream& os, const Form& f);
#endif

Stage 2 - beSigned implementation:

Form.cpp
void Form::beSigned(const Bureaucrat& b) {
if (b.getGrade() > _gradeToSign) // Higher number = lower rank
throw GradeTooLowException();
_signed = true;
}

Stage 3 - signForm in Bureaucrat:

Bureaucrat.cpp
void Bureaucrat::signForm(Form& f) {
try {
f.beSigned(*this);
std::cout << _name << " signed " << f.getName() << std::endl;
} catch (std::exception& e) {
std::cout << _name << " couldn't sign " << f.getName()
<< " because " << e.what() << std::endl;
}
}
LineCodeWhy
const int _gradeToSignConstRequirements don’t change
b.getGrade() > _gradeToSignCompare gradesHigher grade number = lower rank = can’t sign
f.beSigned(*this)Pass selfBureaucrat passes itself to Form
catch (std::exception& e)Catch any exceptionHandle signing failure

1. Grade comparison backwards

// WRONG - this allows low-rank bureaucrats to sign high-rank forms!
if (b.getGrade() < _gradeToSign)
throw GradeTooLowException();
// RIGHT - higher grade NUMBER means LOWER rank
if (b.getGrade() > _gradeToSign)
throw GradeTooLowException();

2. Wrong output format

// WRONG format
std::cout << _name << " signs " << f.getName();
// RIGHT format (per subject)
std::cout << _name << " signed " << f.getName();
// or
std::cout << _name << " couldn't sign " << f.getName() << " because " << reason;
int main() {
Bureaucrat boss("Boss", 1);
Bureaucrat intern("Intern", 150);
Form importantForm("TPS Report", 50, 25);
std::cout << importantForm << std::endl;
intern.signForm(importantForm); // Should fail
boss.signForm(importantForm); // Should succeed
std::cout << importantForm << std::endl;
return 0;
}
Form.cpp
#include "Form.hpp"
Form::Form(const std::string& name, int gradeToSign, int gradeToExecute)
: _name(name), _signed(false),
_gradeToSign(gradeToSign), _gradeToExecute(gradeToExecute)
{
if (gradeToSign < 1 || gradeToExecute < 1)
throw GradeTooHighException();
if (gradeToSign > 150 || gradeToExecute > 150)
throw GradeTooLowException();
}
Form::Form(const Form& other)
: _name(other._name), _signed(other._signed),
_gradeToSign(other._gradeToSign), _gradeToExecute(other._gradeToExecute) { }
Form& Form::operator=(const Form& other) {
if (this != &other)
_signed = other._signed; // Only signed status can change
return *this;
}
Form::~Form() { }
const std::string& Form::getName() const { return _name; }
bool Form::isSigned() const { return _signed; }
int Form::getGradeToSign() const { return _gradeToSign; }
int Form::getGradeToExecute() const { return _gradeToExecute; }
void Form::beSigned(const Bureaucrat& b) {
if (b.getGrade() > _gradeToSign)
throw GradeTooLowException();
_signed = true;
}
std::ostream& operator<<(std::ostream& os, const Form& f) {
os << "Form " << f.getName()
<< " [signed: " << (f.isSigned() ? "yes" : "no")
<< ", sign grade: " << f.getGradeToSign()
<< ", exec grade: " << f.getGradeToExecute() << "]";
return os;
}

Exercise 02: No, You Need Form 28B, Not 28C…

Section titled “Exercise 02: No, You Need Form 28B, Not 28C…”

Make Form abstract (AForm) and create concrete forms:

  1. ShrubberyCreationForm: Creates ASCII trees in a file
  2. RobotomyRequestForm: 50% chance of success
  3. PresidentialPardonForm: Pardons the target

Each form has specific grade requirements and an execute() method.

Step 1 - Make Form abstract

Rename to AForm, add pure virtual execute().

Step 2 - Create concrete forms

Each inherits from AForm and implements execute().

Step 3 - Check preconditions in execute

  • Form must be signed
  • Executor must have high enough grade

Stage 1 - Abstract Form:

AForm.hpp
class AForm {
// ... same as Form, but add:
public:
class FormNotSignedException : public std::exception {
public:
const char* what() const throw() { return "Form not signed!"; }
};
virtual void execute(Bureaucrat const& executor) const = 0; // Pure virtual
protected:
void checkExecution(Bureaucrat const& executor) const; // Helper
};

Stage 2 - Execution check helper:

AForm.cpp
void AForm::checkExecution(Bureaucrat const& executor) const {
if (!_signed)
throw FormNotSignedException();
if (executor.getGrade() > _gradeToExecute)
throw GradeTooLowException();
}

Stage 3 - Concrete form (ShrubberyCreationForm):

ShrubberyCreationForm.hpp
#ifndef SHRUBBERYCREATIONFORM_HPP
#define SHRUBBERYCREATIONFORM_HPP
#include "AForm.hpp"
class ShrubberyCreationForm : public AForm {
private:
std::string _target;
public:
ShrubberyCreationForm(const std::string& target);
ShrubberyCreationForm(const ShrubberyCreationForm& other);
ShrubberyCreationForm& operator=(const ShrubberyCreationForm& other);
~ShrubberyCreationForm();
void execute(Bureaucrat const& executor) const;
};
#endif
ShrubberyCreationForm.cpp
#include "ShrubberyCreationForm.hpp"
#include <fstream>
// Sign grade: 145, Execute grade: 137
ShrubberyCreationForm::ShrubberyCreationForm(const std::string& target)
: AForm("ShrubberyCreationForm", 145, 137), _target(target) { }
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const {
checkExecution(executor); // Throws if not allowed
std::ofstream file((_target + "_shrubbery").c_str());
file << " _-_" << std::endl;
file << " /~~ ~~\\" << std::endl;
file << " { @ @ }" << std::endl;
file << " \\ _ /" << std::endl;
file << " `\\ /`" << std::endl;
file << " | |" << std::endl;
file << " /||\\" << std::endl;
file.close();
}

Stage 4 - RobotomyRequestForm:

RobotomyRequestForm.cpp
#include "RobotomyRequestForm.hpp"
#include <cstdlib>
#include <ctime>
// Sign grade: 72, Execute grade: 45
RobotomyRequestForm::RobotomyRequestForm(const std::string& target)
: AForm("RobotomyRequestForm", 72, 45), _target(target) { }
void RobotomyRequestForm::execute(Bureaucrat const& executor) const {
checkExecution(executor);
std::cout << "* DRILLING NOISES *" << std::endl;
if (std::rand() % 2)
std::cout << _target << " has been robotomized successfully!" << std::endl;
else
std::cout << "Robotomy of " << _target << " failed!" << std::endl;
}

Stage 5 - Add executeForm to Bureaucrat:

Bureaucrat.cpp
void Bureaucrat::executeForm(AForm const& form) {
try {
form.execute(*this);
std::cout << _name << " executed " << form.getName() << std::endl;
} catch (std::exception& e) {
std::cout << _name << " couldn't execute " << form.getName()
<< " because " << e.what() << std::endl;
}
}
LineCodeWhy
virtual void execute(...) const = 0Pure virtualMakes AForm abstract
checkExecution(executor)Helper callReusable validation
std::rand() % 250/50 chanceRandom success for Robotomy
.c_str()String to C-stringC++98 file streams need const char*

1. Forgetting to check preconditions

// WRONG - no validation!
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const {
// Write file...
}
// RIGHT - validate first
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const {
checkExecution(executor); // May throw
// Write file...
}

2. Not seeding random

int main() {
std::srand(std::time(NULL)); // Seed random number generator
// ...
}
int main() {
std::srand(std::time(NULL));
Bureaucrat president("President", 1);
Bureaucrat clerk("Clerk", 140);
ShrubberyCreationForm shrub("home");
RobotomyRequestForm robot("Bender");
PresidentialPardonForm pardon("Ford");
// Try to execute unsigned forms
president.executeForm(shrub); // Fails: not signed
// Sign and execute
president.signForm(shrub);
president.executeForm(shrub); // Creates home_shrubbery file
clerk.signForm(robot); // Fails: grade too low
president.signForm(robot);
president.executeForm(robot); // 50% success
return 0;
}
PresidentialPardonForm.cpp
#include "PresidentialPardonForm.hpp"
// Sign grade: 25, Execute grade: 5
PresidentialPardonForm::PresidentialPardonForm(const std::string& target)
: AForm("PresidentialPardonForm", 25, 5), _target(target) { }
PresidentialPardonForm::PresidentialPardonForm(const PresidentialPardonForm& other)
: AForm(other), _target(other._target) { }
PresidentialPardonForm& PresidentialPardonForm::operator=(const PresidentialPardonForm& other) {
AForm::operator=(other);
_target = other._target;
return *this;
}
PresidentialPardonForm::~PresidentialPardonForm() { }
void PresidentialPardonForm::execute(Bureaucrat const& executor) const {
checkExecution(executor);
std::cout << _target << " has been pardoned by Zaphod Beeblebrox." << std::endl;
}

Exercise 03: At Least This Beats Coffee-Making

Section titled “Exercise 03: At Least This Beats Coffee-Making”

Create an Intern class that can create forms using a factory method:

AForm* makeForm(const std::string& name, const std::string& target);

FORBIDDEN: Using if/else chains! Must use function pointers or similar.

Step 1 - Define form creator function type

typedef AForm* (*FormCreator)(const std::string&);

Step 2 - Create static creator functions

One function per form type.

Step 3 - Use parallel arrays

Array of names + array of creators, loop and match.

Stage 1 - Intern class:

Intern.hpp
#ifndef INTERN_HPP
#define INTERN_HPP
#include "AForm.hpp"
class Intern {
public:
Intern();
Intern(const Intern& other);
Intern& operator=(const Intern& other);
~Intern();
AForm* makeForm(const std::string& name, const std::string& target);
};
#endif

Stage 2 - Factory implementation:

Intern.cpp
#include "Intern.hpp"
#include "ShrubberyCreationForm.hpp"
#include "RobotomyRequestForm.hpp"
#include "PresidentialPardonForm.hpp"
#include <iostream>
// Function pointer type
typedef AForm* (*FormCreator)(const std::string&);
// Creator functions
static AForm* createShrubbery(const std::string& target) {
return new ShrubberyCreationForm(target);
}
static AForm* createRobotomy(const std::string& target) {
return new RobotomyRequestForm(target);
}
static AForm* createPresidential(const std::string& target) {
return new PresidentialPardonForm(target);
}
// Factory method
AForm* Intern::makeForm(const std::string& name, const std::string& target) {
// Parallel arrays - NO if/else!
std::string names[] = {
"shrubbery creation",
"robotomy request",
"presidential pardon"
};
FormCreator creators[] = {
createShrubbery,
createRobotomy,
createPresidential
};
for (int i = 0; i < 3; i++) {
if (name == names[i]) {
std::cout << "Intern creates " << name << std::endl;
return creators[i](target);
}
}
std::cerr << "Intern couldn't create form: " << name << " not found" << std::endl;
return NULL;
}
LineCodeWhy
typedef AForm* (*FormCreator)(...)Function pointer typeDefines type for creator functions
static AForm* createShrubbery(...)Static helperNot a member, just a helper function
creators[i](target)Call via pointerInvokes the creator function
return NULLUnknown formGraceful handling of invalid names

1. Using if/else chain (FORBIDDEN)

// WRONG - explicitly forbidden!
if (name == "shrubbery creation")
return new ShrubberyCreationForm(target);
else if (name == "robotomy request")
return new RobotomyRequestForm(target);
// ...
// RIGHT - use function pointers
for (int i = 0; i < 3; i++) {
if (name == names[i])
return creators[i](target);
}

2. Forgetting memory ownership

// Caller owns the returned pointer!
AForm* form = intern.makeForm("robotomy request", "Bob");
// ... use form ...
delete form; // Caller must delete!
int main() {
Intern someRandomIntern;
Bureaucrat boss("Boss", 1);
AForm* form1 = someRandomIntern.makeForm("shrubbery creation", "garden");
AForm* form2 = someRandomIntern.makeForm("robotomy request", "Bender");
AForm* form3 = someRandomIntern.makeForm("presidential pardon", "Ford");
AForm* form4 = someRandomIntern.makeForm("invalid form", "test"); // NULL
if (form1) {
boss.signForm(*form1);
boss.executeForm(*form1);
delete form1;
}
if (form2) {
boss.signForm(*form2);
boss.executeForm(*form2);
delete form2;
}
if (form3) {
boss.signForm(*form3);
boss.executeForm(*form3);
delete form3;
}
return 0;
}
Intern.cpp
#include "Intern.hpp"
#include "ShrubberyCreationForm.hpp"
#include "RobotomyRequestForm.hpp"
#include "PresidentialPardonForm.hpp"
#include <iostream>
Intern::Intern() { }
Intern::Intern(const Intern& other) { (void)other; }
Intern& Intern::operator=(const Intern& other) { (void)other; return *this; }
Intern::~Intern() { }
typedef AForm* (*FormCreator)(const std::string&);
static AForm* createShrubbery(const std::string& target) {
return new ShrubberyCreationForm(target);
}
static AForm* createRobotomy(const std::string& target) {
return new RobotomyRequestForm(target);
}
static AForm* createPresidential(const std::string& target) {
return new PresidentialPardonForm(target);
}
AForm* Intern::makeForm(const std::string& name, const std::string& target) {
std::string names[] = {
"shrubbery creation",
"robotomy request",
"presidential pardon"
};
FormCreator creators[] = {
createShrubbery,
createRobotomy,
createPresidential
};
for (int i = 0; i < 3; i++) {
if (name == names[i]) {
std::cout << "Intern creates " << name << std::endl;
return creators[i](target);
}
}
std::cerr << "Intern couldn't create form: " << name << " not found" << std::endl;
return NULL;
}