Exceptions in C++

Fundamentals of C++ Structured Exception Handling

Like many modern languages, especially object-oriented languages, C++ provides a syntax and mechanism for handling runtime errors in a structured way, using a very common “throw-catch” paradigm. The term exception is used as an abstraction for the concept.
PREREQUISITES — You should already…
  • have some C or introductory C++ programming experience;
  • know how to create and compile C++ programs.

Introduction

Many C++ tutorials and introductory books introduce exception handling at a later stage. The reason for this is because a complete understanding requires prior knowledge about point­ers, ref­er­en­ces and inheritance. However, it is not necessary to be completely conversant with each of those topics. A small overview, enough to help understand exceptions, is presented here to en­able learn­ers to start using exception handling in all their code.

Pointers & References

Pointers and references have much in common: at machine code level, they produce the same code. References allows us to write “cleaner” code, since the compiler will au­to­ma­ti­cal­ly take ad­dres­ses of the initialisers, and apply indirection on use.

Pointers, Indirection and Arithmetic

Pointers are addresses of particular types of values — a pointer type incorporates the type of val­ue it is pointing to, e.g. T* (read as “T pointer”) means the address of a T type value. The most com­mon operation on a pointer expression (which results in an address value), is in­di­rec­tion, which represents a value at that address.

Pointers also enable another kind of arith­me­tic, called “pointer arithmetic”: this states that it is le­gal to add or subtract an integer type value to or from a pointer expression. Given any ex­pres­sion E, which has type T*, then the result of E+/-I, where I is an integer type value, will be cal­cu­lat­ed as: the value-of(E) +/- I * sizeof(T).

References

References are “disguised” pointers. This means that, at assembler level, they work exactly like pointers, but at source code level, this relationship is hidden — it is recognised and treat­ed as a ref­e­ren­ce, when the pattern T& (read as “T reference”) is encountered.

When the type of a variable, member, element, argument or function return, is a reference type, it means that during initialisation, the address of each initialiser is automatically taken, and when­ev­er the variable, member, element, parameter or function return is used, in­di­rec­tion is ap­plied au­to­ma­ti­cal­ly.

In C++11, we not only have lvalue references (T&), we also find rvalue references (T&&), which al­low for a concept called move semantics, involving move constructors and overloaded move op­e­ra­tors. This has no effect on understanding exceptions, however, so we will not elaborate on that here.

Pointer & Reference Conversions

Pointers can be explicitly cast to any other pointer type, using either the C-style cast: (T*)E, or preferably, using: reinterpret_cast<T>(E), or static_cast<T>(E) in some in­stan­ces. They are not au­to­ma­ti­cal­ly converted to any other pointer, except to void* (and one other scen­ario, which we will in­vesti­gate later).

Reference types can, by the same rule, be cast to other reference types, but not to void&, which is not a valid type: reinterpret_cast<T&>(E).

Inheritance

Classes can inherit from one or more base classes. One interesting and extremely use­ful ef­fect of this feature, is that derived class pointers are explicitly cast to their base class point­ers, when used to ini­tia­li­se, assign, pass, or return, a base class pointer.

Since references are really pointers, this also applies to derived class references. Derived class ref­e­ren­ces are au­to­ma­ti­cal­ly converted to their base class references, when used in a base class reference context — ini­tia­li­sa­tion, assignment, argument passing and function returns.

The benefit of this can be seen when we write, for example, a function that takes a base class ref­e­ren­ce or pointer, as parameter. Now we can, without explicit type conversion, pass it eith­er a base class, or a derived class. Even if we pass a derived class pointer or reference, the only ac­ces­si­ble members are those that were inherited from the base class, which is still ex­tre­me­ly use­ful.

Thread of Execution

We can visualise the step-wise execution of a program as a thread that is fixed at the start of a pro­gram’s startup code: it unwinds with a call to main(); then unwinds ever more as state­ments are exe­cu­ted. Calls to functions also unwind this proverbial thread, and calls to other func­tions within them, unwind it even further — to an arbitrary depth. Normally, only return statements from functions can rewind the thread back up again. This trail that the thread leaves, is re­cord­ed on the stack. This is why this rewinding of the thread is often referred to as “un­wind­ing the stack”, be­cause the stack size decreases (it really should have been called “rewinding the stack”).

Exception handling can use this trail and follow it right back to the start, if necessary. In the pro­cess of retracing the thread, the exception handling mechanism will look for a handler in the cur­rent func­tion. If not found, it will perform normal local variable clean-up, before it re­winds the thread to the caller, at which time it repeats the process, until it potentially reach­es main(). If still not caught in main(), the program will terminate with an “unhandled ex­cep­tion” message.

Throwing Exceptions

Instead of having functions return error codes, we can throw an exception. An exception is sim­ply an expression (object) of any type, used as part of the throw expression›; statement. The ex­pres­sion of the throw statement can be any type, but it is not considered good practice to “throw” intrinsic or common library data types.

To reiterate, the statement: throw 123; (int object) and throw "‹message›"; (throwing a char* object) are both legal, but frowned upon. On the other hand, the statement:

is encouraged, since it uses a type from the <stdexcept> header file, which is more mean­ing­ful than int or char*.

Inherit Exception Type

It is important, from the perspective of good programming practice, that the exception’s type indi­cates:

  1. that it is an exception, and
  2. what kind of error was encountered.

Although a little too sim­plis­tic for production code, an empty structure is often used, just to cre­ate a name that indicates an error:

It is common to rather inherit from one of the standard exceptions in <stdexcept>, in par­ti­cu­lar: std::runtime_error. For example:

The main benefit of this, is that the class inherits the virtual what() method. Ex­cep­tion hand­lers can call this method to retrieve the error message created in the throw during con­struc­tion.

There is another version of throw, which takes no expression after it, and is only legal inside catch blocks (dis­cus­sed later): throw;. Mechanically, this merely continues the pro­cess of re­wind­ing the trail thread (or unwinding the stack), as before.

Standard Library Exceptions

All exceptions in the C++ standard library inherit from std::exception, the declaration of which can be found in <exception>. A very common standard exception, is the one thrown by the new op­e­ra­tor: std::bad_alloc.

Although exceptions are thrown throughout the standard library, with classes and functions de­clar­ed in several header files, exceptions are all declared in <stdexcept> (std::exception is the ex­cep­tion).

Try and Catch

Handlers for exceptions are only searched for in code that is specially marked with a try block. The “block” is basically a compound statement. Any number of catch blocks, which contain the ex­cep­tion handler code, can be attached to the try block (physically following it).

Each catch block accepts one argument. If multiple catch blocks are used on the same try block, each of the parameter types must be different. Optionally, a catch block can be spe­ci­fied to catch any exception, of any type, using ellipses (...) as parameter specification.

NOTE — Catch Parameters

Although the syntax does not enforce it, the types of the catch parameters should be a ref­e­ren­ce type. This prevents unnecessary copying of the thrown object.

Care must be taken with the order of the catch blocks, because the handler-finding code does not look for the best match; it only looks for any legal match, starting from the top. This means that, if the first catch has a parameter of type std::exception&, and a second catch block has a parameter of type std::bad_alloc& (which derives from std::exception), the sec­ond block will never be reached. The solution is simply to list catch blocks with the most de­riv­ed ex­cep­tion pa­ra­me­ters first, before the ones with base class reference parameters.

The body of a function, even constructors, destructors and overloaded operators, can consist in its entirety of a try block, followed by one or more of its associated catch blocks.

functryblock.cppFunction Try Block Example

All exceptions that inherit from std::exception, inherit the what() method, which returns a C-style string (const char*). This contains the message thrower code considered useful, at the time it con­struc­ted the thrown exception object.

Rethrow Caught Exceptions

Inside catch blocks, and only inside them, a rethrow can be effected with: throw;. This will con­tin­ue the search for a handler in the caller function, as if the exception was not caught. This is useful when we want to catch an exception, in order to do some clean-up or logging, but we do not want to han­dle it in the current function.

Unhandled Exceptions

When an exception is not handled, i.e., “escapes” main(), the runtime calls a standard library func­tion called std::terminate(). A program can take control of this function, by passing a function pointer to std::set_terminate(). Only one function can be set as the termination function. A point­er to the previous terminate func­tion is subsequently returned from the std::set_terminate() function. The function passed can also be a lambda, but it cannot be a functor. The value of the current func­tion pointer can be obtained with std::get_terminate().

IMPORTANTDeprecated Function

The std::unexpected() family of functions is deprecated since C++11, so avoid using them in new projects.

Final or Sentinel Code

Languages that use memory managers (garbage collectors) to manage memory on the pro­gram­mer’s behalf, generally have an optional clause to the try…catch statement. It is often called finally. The purpose of this construct is to provide a way to desig­nate “code that is gua­ran­teed to run before the function returns, whether an exception was thrown, caught, or not”.

These languages need such a construct, because destruction of objects is not deterministic — the garbage collector will eventually destruct the objects and reclaim the memory. In C++, we simply have to create a local object within a scope, with destructor code that runs when ex­e­cu­tion reaches the end of the scope — guaranteed. As long as we can spe­ci­fy the “fi­nal­ly” code that runs, we have in fact more flexibility in when it is executed.

Simplistic Method

The following code simply creates a local struct type (it could have been a class), and a var­i­ab­le of this type, at the same time. The destructor for the type (~finally()) represents the “fi­nal­ly” code that must run at the end of the current scope — regardless of how the scope was exited: return, throw, or catch.

finally_demo1.cppA Simple “Finally” Pattern

In the example above, the “finally” code is run after the last statement in the function — just be­fore the machine code instructions resume after the call to main(). This is controlled by con­si­der­ing the scope in which the finally variable has been created. If we surround the struct finally… up to the line before the "…returning." message with curly braces (making it a com­pound statement), the code will then run before the last state­ment.

If an exception was thrown in the try block, as above, the code will still execute, but after the code in the catch block has been executed. If you want the code to execute before the catch block, simply move the type declaration and finally variable definition into the try block. Now it will execute be­fore the code in the exception block.

The “finally” pattern above could be moved to the end of the function, even to after the last state­ment, as long as it is not a return statement — and it will still execute as explained. Where in the scope it is placed, is therefore immaterial, since it will still be destructed at the end of the scope.

Preprocessor Finally Pattern

Although not favoured by C++ programmers, the preprocessor can nevertheless produce clean­er code, as evidenced by Qt with its own “pre-pre­pro­ces­sor” (moc). As long as the pre­pro­ces­sor code is not obscure, we have no real problem with it.

finally_demo2.cppA “Finally” Pattern with a Macro

There is no question — it is very clean and convenient, as long as you remember the ad­mo­ni­tions: only one FINALLY per scope. But that is seldom a problem.

Lambda Template Finally

The full power of C++ can be used to implement a “finally” pattern, using templates and lamb­das. Lambdas, of course, are only available since C++11. The _finally template below im­ple­ments good programming conventions, and uses static_assert, for example, to ensure that the “fi­nal­ly” code promises to throw no exceptions. Also, by deleting some constructors, cop­y­ing of the “finally” object is not permitted. Liberal use of rvalue references and std::move() make this very safe.

Those lines containing static_cast<void>(…) at the end of some scopes, illustrate a con­ven­tion which reminds readers that some code will execute at the end of that scope. Fur­ther­more, it sup­press­es “unused variable” warnings from the compiler.

finally_demo3.cppA “Finally” Pattern with Templates and Lambdas
/*!@file  finally_demo3.cpp
*  @brief Illustration of Simple “Finally” Code with a Template
*/
#include <iostream>

using std::cout; using std::cerr; using std::endl;

extern void demo_func(); // function containing “finally” code example.

int main () {
   try{
      cout << "main(): entering `try` block." << endl;
      demo_func();
      cout << "main(): after `demo_func()`." << endl;
      }
   catch (...) {
      cerr << "main(): exception caught." << endl; // not reached
      }
   return EXIT_SUCCESS;
   }

// the following templates would normally be in a header file. the only
// client-usable identifier below, is the `finally()` template function.

template <typename F> struct _finally;
template <typename F> auto finally(F code) -> _finally<F> {
   return { std::move(code) };
   }

template <typename F> struct _finally {
   _finally() = delete;
   _finally(_finally&&) = delete;
   _finally(const _finally&) = delete;
   _finally& operator=(const _finally&) = delete;
   _finally(F func) : finally_code_{std::move(func)} {}
   ~_finally() noexcept {
      static_assert(noexcept(finally_code_()),
         "lamba musn't throw exceptions. use `noexcept`");
      if (!cancel_flag_) try {finally_code_();} catch(...) {}
      }
   void cancel() { cancel_flag_ = true; }
   void enable() { cancel_flag_ = false; }
   private:
      F finally_code_;
      bool cancel_flag_ = false;
   };


void demo_func() {

   cout << "\ndemo_func(): start." << endl;
   auto&& final1{finally([]() noexcept {
      cerr << "\t<< FINALLY CODE 1 >>" << endl;
      cerr << "\t<< MORE FINALLY 1 >>" << endl;
      })};

   // compound statement to create a scope
      { 
      auto&& final2{finally([]() noexcept {
         cerr << "\t<< FINALLY CODE 2 >>" << endl;
         cerr << "\t<< MORE FINALLY 2 >>" << endl;
         })};
      try {
         auto&& final3{finally([]() noexcept {
            cerr << "\t<< FINALLY CODE 3 >>" << endl;
            cerr << "\t<< MORE FINALLY 3 >>" << endl;
            })};
         cout << "demo_func(): code in the `try`" << endl;
         auto&& final4{finally([]() noexcept {
            cerr << "\t<< FINALLY CODE 4 >>" << endl;
            cerr << "\t<< MORE FINALLY 4 >>" << endl;
            })};
         cout << "demo_func(): more code in `try`" << endl;
         final3.cancel();
         throw 123;
         static_cast<void>(final3);
         static_cast<void>(final4);
         }
      catch(...) {
         cerr << "demo_func(): exception caught" << endl;
         }
      static_cast<void>(final2);
      }

   static_cast<void>(final1);
   cout << "demo_func(): returning." << endl;
   }

This is clearly more sophisticated than the previous solutions. It allows for an ar­bi­tra­ry num­ber of “fi­nal­ly” objects, and the finally lambda code can use variables from the outer function (as long as they have been specified in the capture clause — between the square brackets of the lambda). Further­more, it can be arbitrarily cancelled or enabled via the mem­ber func­tions cancel() and enable() re­spec­tive­ly. It does require a more complicated setup, and al­though it might not look as clean, it is never­the­less the recommended pattern to use.

NOTE — Scope of “Finaliser”

If you move the definition of, for example, final4, to after the throw 123; state­ment, it will never be initialised, and therefore never be destructed. This is guaranteed, and if that is the behaviour you want, there is nothing wrong with it. Of course, it would be pointless in the sim­plis­tic example above, but the throw could appear after a conditional statement, for ex­amp­le, and then the concept would be perfectly viable.

And as a final reminder: the “finally” code is executed when it goes it out of scope. It has noth­ing to do with the presence, or absence, of try…catch statements. You could use the con­cept to exe­cute any code, guaranteed, at the end of any scope, even the com­pound state­ment fol­low­ing it­e­ra­tion or se­lec­tion statements. Or simply at the end of a function body block. It should, how­ev­er, be used sparingly.

Summary

Handling exceptions are statements, and just like other statements containing statements, (e.g., if(), while(), for(), etc.), can be nested. And just like these other statements, there is no one cor­rect way to use try-catch statements. It is part of program design to decide where and how to deal with which exceptions. Generally, however, we try to write exception-safe code, es­pe­ci­al­ly in lower level, helper functions, and handle errors higher up the calling chain.

Sometimes we have no choice other than to throw exceptions. Constructors, for example, can on­ly report errors by throwing exceptions. Regardless, many library methods throw (do­cu­men­ted) ex­cep­tions, and they cannot be ignored, so you should catch them, and deal with the prob­lems.


2017-11-18: Update to new admonitions. [brx]
2016-09-23: Created. [brx]