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

// 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!
}

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