Module 05: Exceptions
Key Concepts:
- try, catch, throw keywords
- Exception classes
- std::exception hierarchy
- Nested exceptions
- Exception safety
Why This Matters
Section titled “Why This Matters”Exceptions provide a structured way to handle errors. Instead of checking return codes everywhere, you can separate normal logic from error handling.
Understanding Exception Operators
Section titled “Understanding Exception Operators”This module introduces exception handling syntax. Here’s what each keyword does:
The throw Statement
Section titled “The throw Statement”throw std::runtime_error("Something went wrong!");throwimmediately 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
The try Block
Section titled “The try Block”try { // Code that might throw riskyFunction();} catch (...) { }trydefines a “protected zone” where exceptions can be caught- Any exception thrown inside a
tryblock will be caught by a matchingcatch - Without
try, an exception would terminate the program
The catch Block
Section titled “The catch Block”catch (std::runtime_error& e) { // Handle runtime_error}catch (std::exception& e) { // Handle any standard exception}catch (...) { // Catch ANY exception (unknown type)}catchspecifies 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 throwvoid riskyFunction() throw(std::runtime_error); // Can only throw listed typesthrow()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 usesthrow()because exception handlers need to call it safely - Note: Modern C++ (11+) uses
noexceptinstead, but 42 curriculum uses C++98
Nested Class Syntax with ::
Section titled “Nested Class Syntax with ::”class Bureaucrat {public: class GradeTooHighException : public std::exception { };};
throw Bureaucrat::GradeTooHighException();ClassName::NestedClassaccesses 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
1. The Problem Without Exceptions
Section titled “1. The Problem Without Exceptions”// C-style error handlingint 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 valueint result;if (divide(10, 0, &result) != 0) { // Handle error}// Easy to forget to check!2. Basic Exception Handling
Section titled “2. Basic Exception Handling”double divide(int a, int b) { if (b == 0) throw std::runtime_error("Division by zero!"); return static_cast<double>(a) / b;}try/catch
Section titled “try/catch”try { double result = divide(10, 0); std::cout << "Result: " << result << std::endl;}catch (std::runtime_error& e) { std::cerr << "Error: " << e.what() << std::endl;}Multiple catch Blocks
Section titled “Multiple catch Blocks”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;}3. std::exception Hierarchy
Section titled “3. std::exception Hierarchy”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_errorThe what() Method
Section titled “The what() Method”class std::exception {public: virtual const char* what() const throw(); // Returns error message};4. Custom Exception Classes
Section titled “4. Custom Exception Classes”Basic Custom Exception
Section titled “Basic Custom Exception”class GradeTooHighException : public std::exception {public: const char* what() const throw() { return "Grade is too high!"; }};
// Usageif (grade < 1) throw GradeTooHighException();Nested Exception Class
Section titled “Nested Exception Class”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!"; } };};
// Usagethrow Bureaucrat::GradeTooHighException();Exception with Dynamic Message
Section titled “Exception with Dynamic Message”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(); }};The throw() Exception Specification
Section titled “The throw() Exception Specification”In C++98, throw() declares that a function promises not to throw exceptions:
// Function promises not to throwvoid safeFunction() throw() { // If this throws, std::unexpected() is called -> terminate}
// Function can throw these specific typesvoid riskyFunction() throw(std::runtime_error, std::bad_alloc) { // Can only throw listed types}
// what() signature - promises not to throwconst char* what() const throw();// ^^^^^^^ Exception specificationconst and throw() Together
Section titled “const and throw() Together”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
5. Module 05 Exercises
Section titled “5. Module 05 Exercises”ex00: Bureaucrat
Section titled “ex00: Bureaucrat”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};
// ImplementationBureaucrat::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 operatorstd::ostream& operator<<(std::ostream& os, const Bureaucrat& b) { os << b.getName() << ", bureaucrat grade " << b.getGrade(); return os;}ex01: Form
Section titled “ex01: Form”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; }}ex02: Abstract Form (AForm)
Section titled “ex02: Abstract Form (AForm)”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 formsclass 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 typestatic 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;}Function Pointer Syntax Breakdown
Section titled “Function Pointer Syntax Breakdown”// Return type (*pointer_name)(parameter types)AForm* (*creator)(const std::string&);
// Array of function pointersAForm* (*creators[3])(const std::string&);
// Assigning a function to the pointercreator = &createShrubbery; // or just: creator = createShrubbery;
// Calling through the pointerAForm* form = creator(target); // or: (*creator)(target);6. File I/O with ofstream (ShrubberyCreationForm)
Section titled “6. File I/O with ofstream (ShrubberyCreationForm)”Writing to Files
Section titled “Writing to Files”#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)}ofstream Methods
Section titled “ofstream Methods”std::ofstream file;
// Open filefile.open("filename.txt"); // Default: truncatefile.open("filename.txt", std::ios::app); // Append mode
// Check if openif (!file.is_open()) { /* error */ }if (file.fail()) { /* error */ }
// Writefile << "text" << std::endl;file << 42 << std::endl;
// Closefile.close();7. Exception Safety
Section titled “7. Exception Safety”Levels of Exception Safety
Section titled “Levels of Exception Safety”- No guarantee: May leak resources, leave objects in invalid state
- Basic guarantee: No leaks, objects in valid (but unspecified) state
- Strong guarantee: Operation succeeds or rolls back completely
- No-throw guarantee: Never throws
RAII Pattern (Resource Acquisition Is Initialization)
Section titled “RAII Pattern (Resource Acquisition Is Initialization)”// BAD: Manual resource managementvoid riskyFunction() { int* data = new int[100]; doSomething(); // If this throws, memory leaks! delete[] data;}
// GOOD: RAII - resource tied to object lifetimeclass 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 cleanup8. Best Practices
Section titled “8. Best Practices”// Catch by referencecatch (std::exception& e) { /* ... */ }
// Use specific exception typesthrow 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 pointersthrow 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!”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);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.
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 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 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;}Quick Reference
Section titled “Quick Reference”Exception Syntax
Section titled “Exception Syntax”throw ExceptionType(); // Throw exceptionthrow ExceptionType("message"); // With message
try { /* risky code */ }catch (Type& e) { /* handle */ }catch (...) { /* catch all */ }Custom Exception Template
Section titled “Custom Exception Template”class MyException : public std::exception {public: const char* what() const throw() { return "My error message"; }};42 Rules
Section titled “42 Rules”- Exception classes do NOT need OCF
- All other classes MUST follow OCF
- Use nested classes for related exceptions
Related Concepts
Section titled “Related Concepts”Continue your C++ journey:
- Previous: Module 04: Polymorphism - Review virtual functions and abstract classes
- Next: Module 06: C++ Casts - Learn type-safe casting operators
Key Terms from This Module
Section titled “Key Terms from This Module”Visit the Glossary for definitions of: