Introduction to C++

Exceptions

Overview

Teaching: 20 min
Exercises: 0 min
Questions
  • How can programs deal with exceptional conditions?

Objectives
  • Learn how to protect code from exceptional conditions.

  • Learn the syntax of a try-catch block.

  • Learn about how exception handlers are invoked.

  • Learn about standard exceptions.

Exceptions provide a way to deal with exceptional conditions in your program. An exception is a value used to represent something unusual that has happened in the program. They are usually created in response to a runtime error condition. Exceptions are dealt with by being caught and execution of the program is transferred to a special function called a handler.

To catch an exception, a section of code is enclosed in a special construct called a try block. When an exceptional circumstance arises within that block, an exception is thrown that transfers the control to the exception handler. If no exception is thrown, the code continues normally and all handlers are ignored.

An exception is thrown by using the throw keyword from somewhere inside the try block, or from calling other function in the block. Handlers are declared with the keyword catch, and are placed immediately after the closing brace of the try block.

The following example shows a simple try block in action.

// exceptions
#include <iostream>
using namespace std;

int main () {
  try
  {
    throw 20;
  }
  catch (int e)
  {
    cout << "An exception occurred. Exception Nr. " << e << endl;
  }
  return 0;
}


The code enclosed in the try block is executed and if an exception occurs, control is transferred to the catch block. In this example the code simply throws an exception using the statement:

throw 20;

A throw expression accepts one parameter which is passed as an argument to the exception handler.

The syntax for catch is similar to a regular function with one parameter. The type of this parameter is very important, since the type of the argument passed by the throw expression is checked against it, and only if the types match, will the exception be caught by that handler.

Multiple handlers (i.e., catch expressions) can be chained, each one with a different parameter type. Only the handler whose argument type matches the type of the exception specified in the throw statement is executed.

If an ellipsis ... is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers.

The following shows an example of chaining exception handlers:

try {
  // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }

In this case, the last handler would catch any exception thrown of a type that is neither int nor char.

After an exception has been handled the program, execution resumes after the entire try-catch block, not after the throw statement!.

It is also possible to nest a try-catch block within a try block. If the nested catch block wishes to forward the exception to the next level, it can do so with the statement throw;.

For example:

try {
  try {
      // code here
  }
  catch (int n) {
      throw;
  }
}
catch (...) {
  cout << "Exception occurred";
}

Standard exceptions

The C++ Standard library provides a base class specifically designed to declare objects to be thrown as exceptions. It is called std::exception and is defined in the <exception> header. This class has a virtual member function called what that returns a null-terminated character sequence (of type char *) and that can be overwritten in derived classes to contain some sort of description of the exception.

// using standard exceptions
#include <iostream>
#include <exception>
using namespace std;

class myexception: public exception
{
  virtual const char* what() const throw()
  {
    return "My exception happened";
  }
} myex;

int main() {
  try
  {
    throw myex;
  }
  catch (exception& e)
  {
    cout << e.what() << endl;
  }
  return 0;
}


In this example, the handler catches exception objects by reference (notice the ampersand & after the type). This allows the handler to also catch classes derived from exception, like our myex object of type myexception.

All exceptions thrown by components of the C++ Standard library throw exceptions derived from this exception class. These are:

exceptiondescription
bad_allocthrown by new on allocation failure
bad_castthrown by dynamic_cast when it fails in a dynamic cast
bad_exceptionthrown by certain dynamic exception specifiers
bad_typeidthrown by typeid
bad_function_callthrown by empty function objects
bad_weak_ptrthrown by shared_ptr when passed a bad weak_ptr

Header <exception> also defines two generic exception types that are also derived from exception. These can be inherited by custom exceptions to report errors:

exceptiondescription
logic_errorerror related to the internal logic of the program
runtime_errorerror detected during runtime

A typical example where exception handling should always be used is when allocating memory:

// bad_alloc standard exception
#include <iostream>
#include <exception>
using namespace std;

int main () {
  try
  {
    int* myarray= new int[1000];
  }
  catch (exception& e)
  {
    cout << "Standard exception: " << e.what() << endl;
  }
  return 0;
}

The exception that may be caught by the exception handler in this example is a bad_alloc. Because bad_alloc is derived from the standard base class exception, it can be caught by using a single handler.

Key Points