Exception Class Hierarchies in C++
How to use exceptions? How to manage error codes in your C++ programs? That is the question of the day.
Luckily c++ exceptions are pretty standard in their programming. The go-to strategy is to create a base exception class with a name of your choosing (KeyException
was mine) and then you can create subclass exception classes, nested in the various principal classes of your codebase. As an example I've created the GameException class which you can use as a template to create other nested exception classes.
Let's start with a tiny C++ exception primer -reminder- though before you get your hands on the code.
You handle exceptions using a try block. If you don't handle an exception it will propagate outwards of its block of code.
try
{
/* code that may throw exception */
}
// start checking more specific exceptions and proceed to more general ones
// This allows you to effectively filter your exception handling.
catch( const std::runtime_error& ex )
{
/* handle the exception `ex`*/
throw; // optional `throw` rethrows the current exception
}
catch( const std::exception& ex )
{
/* handle the exception `ex`*/
throw; // optional `throw` rethrows the current exception
}
Throw an exception by value and catch by reference or const reference.
The only code that is guaranteed to be run after an exception occurred are the destructors of objects residing on the stack. This is due to stack unwinding which clears objects of automatic storage duration that existed in the block where the exception occurred, as well as the blocks of all callers "as the exception propagates outwards".
There are many types of exceptions in C++. I will list the standard ones below for completion sake.
std::exception : interface (debatable if you should catch this)
std::bad_alloc : failure to allocate storage
std::bad_array_new_length : invalid array length
std::bad_cast : execution of an invalid dynamic-cast
std::bad_exception : signifies an incorrect exception was thrown
std::bad_function_call : thrown by "null" std::function
std::bad_typeid : using typeinfo on a null pointer
std::bad_weak_ptr : constructing a shared_ptr from a bad weak_ptr
std::logic_error : errors detectable before the program executes
std::domain_error : parameter outside the valid range
std::future_error : violated a std::promise/std::future condition
std::invalid_argument : invalid argument
std::length_error : length exceeds its maximum allowable size
std::out_of_range : argument value not in its expected range
std::runtime_error : errors detectable when the program executes
std::overflow_error : arithmetic overflow error.
std::underflow_error : arithmetic underflow error.
std::range_error : range errors in internal computations
std::regex_error : errors from the regular expression library.
std::system_error : from operating system or other C API
std::ios_base::failure : Input or output error
noexcept
functions never throw exceptions. Destructors are by default noexcept. You must never allow exceptions to "escape" your destructors. But if you define your own destructor then you should mark it as noexcept. In principle, use noexcept as much as you possibly can. It may even result in a minor performance improvement, because you explicitly tell the compiler to make more safe assumptions about your code.
There is no hard and fast rule, but I'll give you my (and many others) rule of thumb: Assertions should be used for programmer coding errors. Exceptions are used for run time error conditions and provide an indication to the end user (eg. via a message box) as to what went wrong, even in Release mode or the final software product. It may be a programmer bug or a user hitting an exceptional unexpected roadblock in their usage of the program.
Don't be hesitant to use exception handling because of performance reasons. There is no performance hindrance caused by exceptions, unless one occurs, but when that happens an "exceptional" condition has been triggered and at that point the exception can take an arbitrarily long amount of time; but performance is the last thing you should care about by then.
That being said, you should use both exceptions & assertions as much as possible especially in a complicated code base;. Last but not least assertions are (typically) a debug mode construct only - while exceptions can happen in any configuration.
I used Windows, Visual Studio & C++17 to build the project.
Github
Github repository link.
Acknowledgements
Exception Safe Code - by Jon Kalb 3 part CppCon series on YouTube.