This project has been created as part of the 42 curriculum by baelgadi.
Repetition and Exceptions
An uncaught throw doesn’t slow down. Rather, it tears through everything until something grabs it.
That’s the Speed Force for you; step in and you’re gone.
A catch block is the lightning rod, for without it, std::terminate() and you’re not coming back.
|
CPP Module 05:
4 exercises. C++98 standard only. Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98Exercise 00: Mommy, when I grow up, I want to be a bureaucrat! · ex00/ · Bureaucrat + nested exception classesExercise 01: Form up, maggots! · ex01/ · Form, beSigned, const membersExercise 02: No, you need form 28B, not 28C · ex02/ · AForm becomes abstract + 3 concrete formsExercise 03: At least this beats coffee-making · ex03/ · Intern and makeFormForbidden: using namespace (-42) · friend (-42) · printf · *alloc · free · STL containers · STL algorithms · function bodies in headersRequired: Orthodox Canonical Form on every class · every exception derives from std::exception · what() is const throw() · a virtual destructor on AForm |
In C, failure was merely a return value. -1, NULL, errno and a caller who might read it… or might not.
The error travelled at the same speed as everything else, and it could be ignored at every step.
An exception doesn’t travel like that.
if (_grade < 1)
throw GradeTooHighException(); // the function does not return, it's gonethrow abandons the current function immediately, so no return value & no remaining statements.
The runtime then walks back up the call stack and while looking for a handler, it destroys every local object it passes on the way.
try
{
Bureaucrat b("Wally", 0); // throws before b ever exists
std::cout << b << std::endl; // never runs
}
catch (const std::exception &e) // the lightning rod
{
std::cerr << "Exception: " << e.what() << std::endl;
}| - | Return code (C) | Exception (C++) |
|---|---|---|
| Can the caller ignore it? | Yes, silently | No, it keeps climbing |
| Works in a constructor? | No, constructors return nothing | Yes |
| Does it carry a message? | Only via errno |
Yes, what() |
| Cost when nothing fails | A comparison per call | Nada |
[!WARNING] If no
catchon the stack matches, the search ends andstd::terminate()is called.
No lightning rod, no way back.
You can throw anything: an int, a string, a Bureaucrat…
class Bureaucrat
{
public:
class GradeTooHighException : public std::exception
{
public:
virtual const char *what() const throw();
};
class GradeTooLowException : public std::exception
{
public:
virtual const char *what() const throw();
};
};Both classes are nested inside Bureaucrat and that’s deliberate.
AForm has exceptions with the exact same names and nesting keeps them apart:
Bureaucrat::GradeTooHighException // the one about a person
AForm::GradeTooHighException // the one about a documentThe definition lives in the .cpp, like every other member function:
const char *Bureaucrat::GradeTooHighException::what() const throw()
{
return "Grade is too high (minimum is 1)";
}That signature has 3 parts and none of them are optional.
| Part | Why |
|---|---|
const char * |
The base returns a C string, so the override must also |
const |
The base’s what() is const |
throw() |
In C++98 this is the way of saying “this never throws” |
[!IMPORTANT]
throw()is the C++98 spelling ofnoexcept(C++11) which is obviously not authorized here.
[!NOTE] Inheriting from
std::exceptionmeans a singlecatch (const std::exception &e)handles every exception in the module, ours and the standard library’s alike.
Barry ran into the Speed Force once.
There was no Barry left standing afterwards, and perhaps there never was a Barry at all.
A constructor that throws works exactly the same way.
Bureaucrat::Bureaucrat(const std::string &name, int grade)
: _name(name), _grade(grade)
{
if (_grade < 1)
throw GradeTooHighException();
if (_grade > 150)
throw GradeTooLowException();
}The validation is in the body, after the initialization list, since _name and _grade are set before the body runs.
Then the important part:
Bureaucrat b("Wally", 0); // throws
// ~Bureaucrat() is NEVER called (b never came into existence)The object was never fully constructed so it is never destroyed.
What is cleaned up is every member that already finished constructing: _name is a fully built std::string by the time the body throws, so its destructor runs and its memory is freed.
| Already constructed members | The object itself | |
|---|---|---|
| Destroyed on throw? | Yes | No |
| Who handles it | The compiler | Nobody |
[!WARNING] If a constructor does
_buf = new char[42];and then throws, you’ve got yourself a memory leak. No destructor will ever run to free it.
CPP05 does not allocate in constructors, so this is safe here.
ex01 has a Form we can build. ex02 renames it AForm and makes it impossible to build.
Barry is not “a speedster” in the abstract. He is the Flash, or nothing at all.
class AForm
{
public:
void execute(const Bureaucrat &executor) const; // public, concrete
protected:
virtual void executeAction() const = 0; // pure virtual, abstract
};execute() does every check, then hands off:
void AForm::execute(const Bureaucrat &executor) const
{
if (!_signed)
throw FormNotSignedException();
if (executor.getGrade() > _gradeToExecute)
throw GradeTooLowException();
executeAction(); // only then does the derived class get a turn
}This is the Template Method pattern.
The base owns the policy (must be signed / must be senior enough).
The derived class owns only the action.
That’s why a subclass cannot forget to check whether the form was signed. It never gets to make that decision.
executeAction() is protected, not public, so nothing outside the hierarchy can skip the checks by calling it directly.
[!NOTE] ex02 also adds a third exception
FormNotSignedException. Ex01 has noexecute(), so it has nothing to refuse yet.
[!WARNING]
virtual ~AForm();is mandatory. In ex03 the Intern hands us anAForm*that really points at aShrubberyCreationForm, and wedeleteit through the base pointer.
(Without a virtual destructor that is undefined behavior, not just a leak)
Three concrete forms, three different thresholds. Each one passes its own name and grades up to AForm and implements exactly one function.
| Form | Sign | Execute | executeAction()’s action |
|---|---|---|---|
| ShrubberyCreationForm | 145 | 137 | Create <target>_shrubbery in cwd with an ASCII tree |
| RobotomyRequestForm | 72 | 45 | Drilling noises, then succeed 50% of the time |
| PresidentialPardonForm | 25 | 5 | Announce the pardon by Zaphod Beeblebrox (hate that guy) |
ShrubberyCreationForm::ShrubberyCreationForm(const std::string &target)
: AForm("ShrubberyCreationForm", 145, 137), _target(target) {}Bureaucrat b("Wally", 140);
ShrubberyCreationForm f("home");
b.signForm(f); // 140 <= 145 → signed
b.executeForm(f); // 140 > 137 → refusedThe shrubbery writes to a real file, so it is the only form that could fail for a reason that has nothing to do with grades:
std::ofstream os(filename.c_str());
if (!os.is_open())
throw std::runtime_error("cannot open file " + filename);[!NOTE] The
RobotomyRequestFormseedsstd::srandonce, guarded by astatic boolto know if it has been seeded.
Seeding on every call withstd::time(NULL)would give the same “random” answer for every execution inside the same second.
PRNG vs TRNG ;-)
The Intern has no name, no grade and no state. We don’t care who they are and we only care that we can hand them a string & get a form back.
AForm *Intern::makeForm(const std::string &formName, const std::string &target) const;The naive version is an if/else chain, 3 strcmps deep, but the subject forbids it.
The version in ex03/Intern.cpp is a lookup table instead:
struct FormEntry
{
const char *name;
AForm *(*create)(const std::string &); // ptr to a function
};
static const FormEntry forms[] = {
{ "shrubbery creation", makeShrubbery },
{ "robotomy request", makeRobotomy },
{ "presidential pardon", makePresidential }
};
for (int i = 0; i < 3; i++)
{
if (formName == forms[i].name)
return forms[i].create(target);
}
return NULL;makeForm does new. It doesn’t delete.
The caller does:
AForm *form = someRandomIntern.makeForm("robotomy request", "Victor Stone");
if (form)
{
b.signForm(*form);
b.executeForm(*form);
delete form;
}[!WARNING] An unknown form name returns
NULL.
delete NULL;is safe and does nothing butb.signForm(*form)on a null pointer is a segfault.
# ──── ex00 ────
# Verify: grade 0 throws GradeTooHigh, grade 151 throws GradeTooLow
# Verify: incrementing a grade - 1 bureaucrat throws and decrementing a grade - 150 one throws
# Verify: operator<< prints "<name>, bureaucrat grade <grade>."
cd ex00 && make
./bureaucrat
valgrind --leak-check=full ./bureaucrat
# ──── ex01 ────
# Verify: a form built with grade 0 or 151 throws at construction
# Verify: a bureaucrat with a grade that's too low fails to sign (and the form stays unsigned)
# Verify: signing an already signed form is harmless no-op
cd ex01 && make
./form
valgrind --leak-check=full ./form
# ──── ex02 ────
# Verify: 'AForm f("x", 1, 1);' fails to compile (abstract)
# Verify: executing an unsigned form throws FormNotSignedException
# Verify: the shrubbery file actually appears on disk and has ASCII trees
cd ex02 && make
./aform
cat home_shrubbery
valgrind --leak-check=full ./aform
# ──── ex03 ────
# Verify: all 3 known form names produce the right form
# Verify: an unknown name returns NULL
cd ex03 && make
./intern
valgrind --leak-check=full ./internthrow expressionstd::exceptionthrow())You can run the whole module in an afternoon but speed isn’t what is being graded here.
What is graded is whether every throw has a lightning rod waiting for it.
Hey, the module went down, and nobody got hurt. You know what I call that? A really good day.