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!”Subject Analysis
Section titled “Subject Analysis”Create a Bureaucrat class with:
- Name: Constant, set at construction
- Grade: 1 (highest) to 150 (lowest)
- Exceptions:
GradeTooHighExceptionandGradeTooLowException
Key methods:
incrementGrade(): Makes grade BETTER (lower number)decrementGrade(): Makes grade WORSE (higher number)- Both throw if grade would become invalid
Approach Strategy
Section titled “Approach Strategy”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.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Exception classes:
#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);
#endifStage 2 - Constructor with validation:
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:
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}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
const std::string _name | Const member | Name can’t change after construction |
: public std::exception | Inherit | Allows catching with catch (std::exception&) |
const char* what() const throw() | Override | Standard exception interface |
throw GradeTooHighException() | Throw exception | Create and throw exception object |
_grade-- | Increment = lower | Grade 1 is best, so “up” means lower number |
Common Pitfalls
Section titled “Common Pitfalls”1. Grade logic backwards
// WRONG - increment should make grade BETTER (lower)void Bureaucrat::incrementGrade() { _grade++; // This makes grade worse!}
// RIGHTvoid Bureaucrat::incrementGrade() { _grade--; // Lower number = better grade}2. Exception class not inheriting std::exception
// WRONG - can't catch with std::exceptionclass GradeTooHighException { };
// RIGHT - inherits from std::exceptionclass GradeTooHighException : public std::exception { const char* what() const throw() { return "Grade is too high!"; }};3. Forgetting to validate in constructor
// WRONG - allows invalid gradesBureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade) { }
// RIGHT - validate firstBureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade){ if (grade < 1) throw GradeTooHighException(); if (grade > 150) throw GradeTooLowException();}Testing Tips
Section titled “Testing Tips”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;}Final Code
Section titled “Final Code”#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;}Exercise 01: Form Up, Maggots!
Section titled “Exercise 01: Form Up, Maggots!”Subject Analysis
Section titled “Subject Analysis”Create a Form class that:
- Has name, signed status, and two grade requirements
gradeToSign: Minimum grade needed to signgradeToExecute: Minimum grade needed to execute- Bureaucrat can
signForm()- signs if grade is good enough
Approach Strategy
Section titled “Approach Strategy”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.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Form class:
#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);
#endifStage 2 - beSigned implementation:
void Form::beSigned(const Bureaucrat& b) { if (b.getGrade() > _gradeToSign) // Higher number = lower rank throw GradeTooLowException(); _signed = true;}Stage 3 - signForm in Bureaucrat:
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; }}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
const int _gradeToSign | Const | Requirements don’t change |
b.getGrade() > _gradeToSign | Compare grades | Higher grade number = lower rank = can’t sign |
f.beSigned(*this) | Pass self | Bureaucrat passes itself to Form |
catch (std::exception& e) | Catch any exception | Handle signing failure |
Common Pitfalls
Section titled “Common Pitfalls”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 rankif (b.getGrade() > _gradeToSign) throw GradeTooLowException();2. Wrong output format
// WRONG formatstd::cout << _name << " signs " << f.getName();
// RIGHT format (per subject)std::cout << _name << " signed " << f.getName();// orstd::cout << _name << " couldn't sign " << f.getName() << " because " << reason;Testing Tips
Section titled “Testing Tips”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;}Final Code
Section titled “Final Code”#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…”Subject Analysis
Section titled “Subject Analysis”Make Form abstract (AForm) and create concrete forms:
- ShrubberyCreationForm: Creates ASCII trees in a file
- RobotomyRequestForm: 50% chance of success
- PresidentialPardonForm: Pardons the target
Each form has specific grade requirements and an execute() method.
Approach Strategy
Section titled “Approach Strategy”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
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Abstract Form:
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:
void AForm::checkExecution(Bureaucrat const& executor) const { if (!_signed) throw FormNotSignedException(); if (executor.getGrade() > _gradeToExecute) throw GradeTooLowException();}Stage 3 - Concrete form (ShrubberyCreationForm):
#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#include "ShrubberyCreationForm.hpp"#include <fstream>
// Sign grade: 145, Execute grade: 137ShrubberyCreationForm::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:
#include "RobotomyRequestForm.hpp"#include <cstdlib>#include <ctime>
// Sign grade: 72, Execute grade: 45RobotomyRequestForm::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:
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; }}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
virtual void execute(...) const = 0 | Pure virtual | Makes AForm abstract |
checkExecution(executor) | Helper call | Reusable validation |
std::rand() % 2 | 50/50 chance | Random success for Robotomy |
.c_str() | String to C-string | C++98 file streams need const char* |
Common Pitfalls
Section titled “Common Pitfalls”1. Forgetting to check preconditions
// WRONG - no validation!void ShrubberyCreationForm::execute(Bureaucrat const& executor) const { // Write file...}
// RIGHT - validate firstvoid 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 // ...}Testing Tips
Section titled “Testing Tips”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;}Final Code
Section titled “Final Code”#include "PresidentialPardonForm.hpp"
// Sign grade: 25, Execute grade: 5PresidentialPardonForm::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”Subject Analysis
Section titled “Subject Analysis”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.
Approach Strategy
Section titled “Approach Strategy”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.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Intern class:
#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);};
#endifStage 2 - Factory implementation:
#include "Intern.hpp"#include "ShrubberyCreationForm.hpp"#include "RobotomyRequestForm.hpp"#include "PresidentialPardonForm.hpp"#include <iostream>
// Function pointer typetypedef AForm* (*FormCreator)(const std::string&);
// Creator functionsstatic 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 methodAForm* 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;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
typedef AForm* (*FormCreator)(...) | Function pointer type | Defines type for creator functions |
static AForm* createShrubbery(...) | Static helper | Not a member, just a helper function |
creators[i](target) | Call via pointer | Invokes the creator function |
return NULL | Unknown form | Graceful handling of invalid names |
Common Pitfalls
Section titled “Common Pitfalls”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 pointersfor (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!Testing Tips
Section titled “Testing Tips”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;}Final Code
Section titled “Final Code”#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;}