Skip to content

Module 05: Exceptions

Download Official Subject PDF

Key Concepts:

  • try, catch, throw keywords
  • Exception classes
  • std::exception hierarchy
  • Nested exceptions
  • Exception safety

Exceptions provide a structured way to handle errors. Instead of checking return codes everywhere, you can separate normal logic from error handling.


This module introduces exception handling syntax. Here’s what each keyword does:

throw std::runtime_error("Something went wrong!");
  • throw immediately stops normal execution and “throws” an exception object
  • The exception “bubbles up” through the call stack until something catches it
  • If nothing catches it, the program terminates with an error
  • You can throw any type, but it’s best practice to throw exception classes
try {
// Code that might throw
riskyFunction();
} catch (...) { }
  • try defines a “protected zone” where exceptions can be caught
  • Any exception thrown inside a try block will be caught by a matching catch
  • Without try, an exception would terminate the program
catch (std::runtime_error& e) {
// Handle runtime_error
}
catch (std::exception& e) {
// Handle any standard exception
}
catch (...) {
// Catch ANY exception (unknown type)
}
  • catch specifies how to handle specific exception types
  • & means “catch by reference” (avoids copying, recommended)
  • ... (three dots) means “catch everything” - use as a last resort
  • Order matters: put specific exceptions first, general ones last

The throw() Exception Specification (C++98)

Section titled “The throw() Exception Specification (C++98)”
const char* what() const throw(); // Promises not to throw
void riskyFunction() throw(std::runtime_error); // Can only throw listed types
  • throw() after a function declaration is an exception specification (C++98 only)
  • Empty throw() means “I promise this function will never throw”
  • throw(Type) means “I can only throw these specific types”
  • The what() method uses throw() because exception handlers need to call it safely
  • Note: Modern C++ (11+) uses noexcept instead, but 42 curriculum uses C++98
class Bureaucrat {
public:
class GradeTooHighException : public std::exception { };
};
throw Bureaucrat::GradeTooHighException();
  • ClassName::NestedClass accesses a class defined inside another class
  • :: is the scope resolution operator (same as with namespaces)
  • Nested exception classes keep related exceptions organized
  • The nested class can be defined inside the header or outside

// C-style error handling
int divide(int a, int b, int* result) {
if (b == 0)
return -1; // Error code
*result = a / b;
return 0; // Success
}
// Caller must check every return value
int result;
if (divide(10, 0, &result) != 0) {
// Handle error
}
// Easy to forget to check!

double divide(int a, int b) {
if (b == 0)
throw std::runtime_error("Division by zero!");
return static_cast<double>(a) / b;
}
try {
double result = divide(10, 0);
std::cout << "Result: " << result << std::endl;
}
catch (std::runtime_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
try {
// code that might throw
}
catch (std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << std::endl;
}
catch (std::out_of_range& e) {
std::cerr << "Out of range: " << e.what() << std::endl;
}
catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
catch (...) {
std::cerr << "Unknown exception!" << std::endl;
}

std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
└── std::runtime_error
├── std::range_error
└── std::overflow_error
class std::exception {
public:
virtual const char* what() const throw(); // Returns error message
};

class GradeTooHighException : public std::exception {
public:
const char* what() const throw() {
return "Grade is too high!";
}
};
// Usage
if (grade < 1)
throw GradeTooHighException();
class Bureaucrat {
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!";
}
};
};
// Usage
throw Bureaucrat::GradeTooHighException();
class FormException : public std::exception {
private:
std::string _message;
public:
FormException(const std::string& msg) : _message(msg) {}
virtual ~FormException() throw() {}
const char* what() const throw() {
return _message.c_str();
}
};

In C++98, throw() declares that a function promises not to throw exceptions:

// Function promises not to throw
void safeFunction() throw() {
// If this throws, std::unexpected() is called -> terminate
}
// Function can throw these specific types
void riskyFunction() throw(std::runtime_error, std::bad_alloc) {
// Can only throw listed types
}
// what() signature - promises not to throw
const char* what() const throw();
// ^^^^^^^ Exception specification

The what() method uses both:

const char* what() const throw();
// ^^^^^ ^^^^^^
// | Exception specification
// const member function (doesn't modify object)

Why what() uses throw():

  • Exception handling code calls what() to get error message
  • If what() itself threw, it would cause problems during exception handling
  • Promise to never throw ensures safe error message retrieval

class Bureaucrat {
private:
const std::string _name;
int _grade; // 1 (highest) to 150 (lowest)
public:
class GradeTooHighException : public std::exception {
public:
const char* what() const throw();
};
class GradeTooLowException : public std::exception {
public:
const char* what() const throw();
};
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(); // Throws if < 1
void decrementGrade(); // Throws if > 150
};
// Implementation
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade)
{
if (grade < 1)
throw GradeTooHighException();
if (grade > 150)
throw GradeTooLowException();
}
void Bureaucrat::incrementGrade() {
if (_grade - 1 < 1)
throw GradeTooHighException();
_grade--;
}
// Stream operator
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b) {
os << b.getName() << ", bureaucrat grade " << b.getGrade();
return os;
}
class Form {
private:
const std::string _name;
bool _signed;
const int _gradeToSign;
const int _gradeToExecute;
public:
class GradeTooHighException : public std::exception { /* ... */ };
class GradeTooLowException : public std::exception { /* ... */ };
Form(const std::string& name, int signGrade, int execGrade);
// ... OCF ...
const std::string& getName() const;
bool isSigned() const;
int getGradeToSign() const;
int getGradeToExecute() const;
void beSigned(const Bureaucrat& b);
};
// In Bureaucrat class:
void Bureaucrat::signForm(Form& form) {
try {
form.beSigned(*this);
std::cout << _name << " signed " << form.getName() << std::endl;
}
catch (std::exception& e) {
std::cout << _name << " couldn't sign " << form.getName()
<< " because " << e.what() << std::endl;
}
}
class AForm {
protected:
// ... same as Form ...
virtual void execute(Bureaucrat const& executor) const = 0;
void checkExecutability(const Bureaucrat& executor) const;
public:
// ... OCF ...
void beSigned(const Bureaucrat& b);
class GradeTooHighException : public std::exception { /* ... */ };
class GradeTooLowException : public std::exception { /* ... */ };
class FormNotSignedException : public std::exception { /* ... */ };
};
// Concrete forms
class ShrubberyCreationForm : public AForm {
public:
ShrubberyCreationForm(const std::string& target);
void execute(Bureaucrat const& executor) const;
};
class RobotomyRequestForm : public AForm {
// 50% success rate
};
class PresidentialPardonForm : public AForm {
// Pardon by Zaphod Beeblebrox
};

ex03: Intern (Function Pointers for Factory)

Section titled “ex03: Intern (Function Pointers for Factory)”
class Intern {
public:
Intern();
Intern(const Intern& other);
Intern& operator=(const Intern& other);
~Intern();
AForm* makeForm(const std::string& formName, const std::string& target);
};
// Helper functions that create each form type
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);
}
// Implementation using function pointers (no if/else forest)
AForm* Intern::makeForm(const std::string& formName, const std::string& target) {
// Parallel arrays: names and corresponding creator functions
std::string names[3] = {
"shrubbery creation",
"robotomy request",
"presidential pardon"
};
// Array of function pointers
// Type: pointer to function taking const string& and returning AForm*
AForm* (*creators[3])(const std::string&) = {
&createShrubbery,
&createRobotomy,
&createPresidential
};
for (int i = 0; i < 3; i++) {
if (formName == names[i]) {
std::cout << "Intern creates " << formName << std::endl;
return creators[i](target); // Call through function pointer
}
}
std::cerr << "Form '" << formName << "' not found" << std::endl;
return NULL;
}
// Return type (*pointer_name)(parameter types)
AForm* (*creator)(const std::string&);
// Array of function pointers
AForm* (*creators[3])(const std::string&);
// Assigning a function to the pointer
creator = &createShrubbery; // or just: creator = createShrubbery;
// Calling through the pointer
AForm* form = creator(target); // or: (*creator)(target);

6. File I/O with ofstream (ShrubberyCreationForm)

Section titled “6. File I/O with ofstream (ShrubberyCreationForm)”
#include <fstream>
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const {
// Check if form can be executed
checkExecutability(executor);
// Create output file: target_shrubbery
std::string filename = _target + "_shrubbery";
std::ofstream file(filename.c_str()); // .c_str() for C++98
if (!file.is_open()) {
throw std::runtime_error("Cannot create file");
}
// Write ASCII trees to file
file << " _-_" << std::endl;
file << " /~~ ~~\\" << std::endl;
file << " /~~ ~~\\" << std::endl;
file << "{ }" << std::endl;
file << " \\ _- -_ /" << std::endl;
file << " ~ \\\\ // ~" << std::endl;
file << "_- - | | _- _" << std::endl;
file << " _ - | | -_" << std::endl;
file << " // \\\\" << std::endl;
file.close(); // Good practice (also closes automatically on destruction)
}
std::ofstream file;
// Open file
file.open("filename.txt"); // Default: truncate
file.open("filename.txt", std::ios::app); // Append mode
// Check if open
if (!file.is_open()) { /* error */ }
if (file.fail()) { /* error */ }
// Write
file << "text" << std::endl;
file << 42 << std::endl;
// Close
file.close();

  1. No guarantee: May leak resources, leave objects in invalid state
  2. Basic guarantee: No leaks, objects in valid (but unspecified) state
  3. Strong guarantee: Operation succeeds or rolls back completely
  4. No-throw guarantee: Never throws

RAII Pattern (Resource Acquisition Is Initialization)

Section titled “RAII Pattern (Resource Acquisition Is Initialization)”
// BAD: Manual resource management
void riskyFunction() {
int* data = new int[100];
doSomething(); // If this throws, memory leaks!
delete[] data;
}
// GOOD: RAII - resource tied to object lifetime
class SafeArray {
private:
int* _data;
public:
SafeArray(int size) : _data(new int[size]) {}
~SafeArray() { delete[] _data; }
};
void safeFunction() {
SafeArray data(100);
doSomething(); // If this throws, SafeArray destructor still runs
} // Automatic cleanup

// Catch by reference
catch (std::exception& e) { /* ... */ }
// Use specific exception types
throw GradeTooHighException();
// Document exceptions in comments
/** @throws GradeTooHighException if grade < 1 */
void setGrade(int grade);
// Don't catch by value (slicing!)
catch (std::exception e) { /* ... */ } // BAD
// Don't throw pointers
throw new MyException(); // Memory leak risk
// Don't throw in destructors
~MyClass() {
throw std::runtime_error("Error"); // Terminates program!
}

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);

Subject expectation: avoid a huge if/else if/else chain (it’s considered messy and won’t be accepted during evaluation). Use a lookup table (function pointers or similar) instead.

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 a big if/else if chain (not accepted during evaluation)

// WRONG: hard to read/maintain (subject warns against messy if/else chains)
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;
}

throw ExceptionType(); // Throw exception
throw ExceptionType("message"); // With message
try { /* risky code */ }
catch (Type& e) { /* handle */ }
catch (...) { /* catch all */ }
class MyException : public std::exception {
public:
const char* what() const throw() {
return "My error message";
}
};
  • Exception classes do NOT need OCF
  • All other classes MUST follow OCF
  • Use nested classes for related exceptions

Continue your C++ journey:

Visit the Glossary for definitions of: