Module 05: Exceptions
Key Concepts:
- try, catch, throw keywords
- Exception classes
- std::exception hierarchy
- Nested exceptions
- Exception safety
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 cleanup7. Best Practices
Section titled “7. 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!}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