Exceptions are the way of flagging unexpected conditions or errors that have occurred in C++ program. C++ Language provides a good mechanism to tackle these conditions. The exception mechanism uses three keywords.

  1. try – It identifies a code block in which an exception can occur
  2. catch – Identifies a block of code in which the exception is handled
  3. throw – It causes an exception condition to be originated

Table of Contents

Introduction to Exception Handling

In this tutorial, we will explore C++ exception handling by considering a simple example. Imagine you are writing a program to handle calendar dates and want to ensure that a given year is within a specific range. In this updated example, the program checks whether the provided year is between 1900 and 2024. If the year falls outside this range, a custom exception (DateException) is thrown, and the error message is printed. The main function catches the exception, displays the error message, and returns a non-zero exit code. Feel free to modify the year in the f function to test the exception handling.

The basic idea here is that we have a try block:

Within this block, we execute some code, in this case a function call f().
Then we have a list of one or more handlers:

If an abnormal condition arises in the code, we can throw an exception:

and have it caught by one of the handlers at an outer level, that is, execution will continue at the point of the handler, with the execution stack unwound.

An exception may be a class object type such as DateException, or a fundamental C++ type like an integer. Obviously, a class object type can store and convey more information about the nature of the exception, as illustrated in this example. Saying: throw -37;

We will explore various details of exception handling later, but one general comment is in order. C++ exceptions are not the same as low-level hardware interrupts, nor are they the same as UNIX signals such as SIGTERM. And there’s no linkage  between exceptions such as divide by zero (which may be a low-level machine exception) and C++ exceptions.

Throwing an Exception

Throwing an exception transfers control to an exception handler. For example:

In this example the exception with value 37 is thrown, and control passes to the handler. A throw transfers control to the nearest handler with the appropriate type. “Nearest” means in the sense of stack frames and try blocks that have been dynamically entered.

A class object instance that is thrown is treated similarly to a function argument or operand in a return statement. A temporary copy of the instance may be made at the throw point, just as temporaries are sometimes used with function argument passing. A copy constructor if any is used to initialize the temporary, with the class’s destructor used to destruct the temporary. The temporary persists as long as there is a handler being executed for the given exception. As in other parts of the C++ language, some compilers may be able in some cases to eliminate the temporary. An example:

When running this program, you can trace through the various stages of throwing the exception, including the actual throw, making a temporary copy of the class instance, and the invocation of the destructor on the temporary. It’s also possible to have “throw” with no argument, as in:

What does this mean? It means that rethrowing an exception uses the existing temporary. The rethrown exception is the most recently caught one that hasn’t been fully processed yet. A caught exception is one where the catch clause’s parameter is initialized and the catch clause hasn’t been exited.

In the given example, “throw;” would rethrow the exception represented by “e.” Since there’s no outer catch clause to handle the rethrown exception, a special library function, terminate(), is invoked. If an exception is rethrown and there’s no ongoing exception handling, terminate() is also called.

Stack Unwinding in C++

Exception handling in C++ involves dynamically managing the flow of program execution. When an exception is thrown, the control transfers to the nearest suitable handler within the dynamically surrounding try block. In this context, “nearest” refers to the try block containing a handler that matches the type of the thrown exception. In future discussions, we’ll delve deeper into exception handlers.

When control is transferred to an exception handler, it implies moving from one program context to another. The question arises: what about the cleanup of the old program context? Specifically, what happens to locally allocated class objects? The answer is “yes.” All stack allocated (“automatic”) objects allocated since entering the try block will have their destructors invoked.

Let’s illustrate this with an example:

Output of this program is:

In this example, when an exception is thrown in the f function, control transfers to the catch clause in the main function. The destructors of the allocated objects (a1 and a3) are called, while a2 and a4 are not, as they were never allocated.

Note: If class objects contain other objects or arrays with partial construction followed by an exception, only the constructed subobjects will be destructed.

Unhandled Exceptions – The terminate() and unexpected() Functions

In C++, when an exception is thrown but there’s no handler to catch it, a special function called terminate() is invoked. This occurs when the exception type doesn’t match any available handlers. The terminate() is a library function that, by default, aborts the program. However, you can override it by providing your own terminate() function.

Example of overriding terminate():

Similarly, the function unexpected() can be triggered when a function with an exception specification throws an exception of a type not listed in its specification. By default, unexpected() calls terminate(), but if the user defines their own version of unexpected(), execution can continue.

Example of using unexpected():

There’s also a new library function called uncaught_exception(), which returns true during the evaluation of the object to be thrown until the initialization of the exception declaration in the matching handler. If this function returns true, it indicates ongoing stack unwinding, and throwing another exception would lead to the invocation of terminate().