Author image

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.

    /* 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.

Beginners tend to confuse exceptions with error codes and assertions (assert()). Which one to use and when?

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 repository link.


Exception Safe Code - by Jon Kalb 3 part CppCon series on YouTube.