Search

14.5 — Exceptions, classes, and inheritance

Exceptions and member functions

Up to this point in the tutorial, you’ve only seen exceptions used in non-member functions. However, exceptions are equally useful in member functions, and even moreso in overloaded operators. Consider the following overloaded [] operator as part of a simple integer array class:

Although this function will work great as long as index is a valid array index, this function is sorely lacking in some good error checking. We could add an assert statement to ensure the index is valid:

Now if the user passes in an invalid index, the program will cause an assertion error. While this is useful to indicate to the user that something went wrong, sometimes the better course of action is to fail silently and let the caller know something went wrong so they can deal with it as appropriate.

Unfortunately, because overloaded operators have specific requirements as to the number and type of parameter(s) they can take and return, there is no flexibility for passing back error codes or boolean values to the caller. However, since exceptions do not change the signature of a function, they can be put to great use here. Here’s an example:

Now, if the user passes in an invalid index, operator[] will throw an int exception.

When constructors fail

Constructors are another area of classes in which exceptions can be very useful. If a constructor fails, simply throw an exception to indicate the object failed to create. The object’s construction is aborted and its destructor is never executed (note: this means your exception handler should handle any necessary cleanup).

Exception classes

One of the major problems with using basic data types (such as int) as exception types is that they are inherently vague. An even bigger problem is disambiguation of what an exception means when there are multiple statements or function calls within a try block.

In this example, if we were to catch an int exception, what does that really tell us? Was one of the array indexes out of bounds? Did operator+ cause integer overflow? Did operator new fail because it ran out of memory? Unfortunately, in this case, there’s just no easy way to disambiguate. While we can throw const char* exceptions to solve the problem of identifying WHAT went wrong, this still does not provide us the ability to handle exceptions from various sources differently.

One way to solve this problem is to use exception classes. An exception class is just a normal class that is designed specifically to be thrown as an exception. Let’s design a simple exception class to be used with our IntArray class:

Here’s a full program using this class:

Using such a class, we can have the exception return a description of the problem that occurred, which provides context for what went wrong. And since ArrayException is its own unique type, we can specifically catch exceptions thrown by the array class and treat them differently from other exceptions if we wish.

Note that exception handlers should catch class exception objects by reference instead of by value. This prevents the compiler from making a copy of the exception, which can be expensive when the exception is a class object, and prevents object slicing when dealing with derived exception classes (which we’ll talk about in a moment). Catching exceptions by pointer should generally be avoided unless you have a specific reason to do so.

Exceptions and inheritance

Since it’s possible to throw classes as exceptions, and classes can be derived from other classes, we need to consider what happens when we use inherited classes as exceptions. As it turns out, exception handlers will not only match classes of a specific type, they’ll also match classes derived from that specific type as well! Consider the following example:

In the above example we throw an exception of type Derived. However, the output of this program is:

caught Base

What happened?

First, as mentioned above, derived classes will be caught by handlers for the base type. Because Derived is derived from Base, Derived is-a Base (they have an is-a relationship). Second, when C++ is attempting to find a handler for a raised exception, it does so sequentially. Consequently, the first thing C++ does is check whether the exception handler for Base matches the Derived exception. Because Derived is-a Base, the answer is yes, and it executes the catch block for type Base! The catch block for Derived is never even tested in this case.

In order to make this example work as expected, we need to flip the order of the catch blocks:

This way, the Derived handler will get first shot at catching objects of type Derived (before the handler for Base can). Objects of type Base will not match the Derived handler (Derived is-a Base, but Base is not a Derived), and thus will “fall through” to the Base handler.

Rule: Handlers for derived exception classes should be listed before those for base classes.

The ability to use a handler to catch exceptions of derived types using a handler for the base class turns out to be exceedingly useful.

std::exception

Many of the classes and operators in the standard library throw exceptions classes on failure. For example, operator new and std::string can throw std::bad_alloc if they are unable to allocate enough memory. A failed dynamic_cast will throw std::bad_cast. And so on. As of C++14, there are 21 different exception classes that can be thrown, with more coming in C++17.

The good news is that all of these exception classes are derived from a single class called std::exception. std::exception is a small interface class designed to serve as a base class to any exception thrown by the C++ standard library.

Much of the time, when an exception is thrown by the standard library, we won’t care whether it’s a bad allocation, a bad cast, or something else. We just care that something catastrophic went wrong and now our program is exploding. Thanks to std::exception, we can set up an exception handler to catch exceptions of type std::exception, and we’ll end up catching std::exception and all (21+) of the derived exceptions together in one place. Easy!

The above program prints:

Standard exception: string too long

The above example should be pretty straightforward. The one thing worth noting is that std::exception has a virtual member function named what() that returns a C-style string description of the exception. Most derived classes override the what() function to change the message. Note that this string is meant to be used for descriptive text only -- do not use it for comparisons, as it is not guaranteed to be the same across compilers.

Sometimes we’ll want to handle a specific type of exception differently. In this case, we can add a handler for that specific type, and let all the others “fall through” to the base handler. Consider:

In this example, exceptions of type std::bad_alloc will be caught by the first handler and handled there. Exceptions of type std::exception and all of the other derived classes will be caught by the second handler.

Such inheritance hierarchies allow us to use specific handlers to target specific derived exception classes, or to use base class handlers to catch the whole hierarchy of exceptions. This allows us a fine degree of control over what kind of exceptions we want to handle while ensuring we don’t have to do too much work to catch “everything else” in a hierarchy.

Using the standard exceptions directly

Nothing throws a std::exception directly, and neither should you. However, you should feel free to throw the other standard exception classes in the standard library if they adequately represent your needs. You can find a list of all the standard exceptions on cppreference.

std::runtime_error (included as part of the stdexcept header) is a popular choice, because it has a generic name, and its constructor takes a customizable message:

This prints:

Standard exception: Bad things happened

Deriving your own classes from std::exception

You can, of course, derive your own classes from std::exception, and override the virtual what() member function. Here’s the same program as above, with ArrayException derived from std::exception:

It’s up to you whether you want create your own standalone exception classes, use the standard exception classes, or derive your own exception classes from std::exception. All are valid approaches depending on your aims.

14.6 -- Rethrowing exceptions
Index
14.4 -- Uncaught exceptions, catch-all handlers, and exception specifiers

14 comments to 14.5 — Exceptions, classes, and inheritance

  • Matt

    Catch this… Under "Exception classes", in the first sentence, "problem" should be plural:)

  • Ola Sh

    Hello Alex,

    Thanks again for the good tutorial. I followed your example code below; both the catch handlers in main and Derived were executed by the program. Why does the catch handler in main execute if the catch handler in Derived has already handled the exception? Thanks for your help.

    This prints:

    exception caught in Derived
    exception caught in main

    • Alex

      Function try blocks can not absorb an exception and pretend nothing went wrong. They must either throw an exception of their own, or rethrow the same exception.

      So in the case above, the Derived() catch block is rethrowing the exception so that main() can properly handle the object that failed to construct.

  • Alexander Bieniek

    Do exception handlers treat enumerated types as integers? If not, would they be a similar way to find another type of object to throw?

    Also, are similar data types treated the same way? Would a catch block catch a float if the block was looking for a double?

    • Alex

      You can throw and catch enums:

      A catch block for a double won’t catch a float, nor would a catch block for an integer catch a char or a short.

  • Inc

    Consider giving a short explanation about function-try-blocks http://en.cppreference.com/w/cpp/language/function-try-block

  • Naga Pushkal

    Hi Alex,

    If an exception is occurred in a constructor, How could we handle them? Can you please elaborate your statement in "When constructors fail" section in this chapter with small example if possible?

    Today I was asked below question in an interview:

    There is a "class A". And "class B" is derived from "class A". An exception is occurred in class A’s constructor. So how would "class B" knows about this exception?

    BTW, Merry Christmas 🙂

    • Alex

      Great questions!

      For normal cases, you’d just ensure creation of the object happened in a try block, catch the exception and handle it:

      The second question is a little more challenging. I think for this, the only solution would be to use a function level try block. See http://en.cppreference.com/w/cpp/language/function-try-block for details.

      Here’s a sample I wrote:

      This prints:

      exception caught in Derived
      exception caught in main
      
  • tata

    Since the object below is anonymous,

    why shouldn’t the code below fail, due to the anonymous object being out of its expression scope

    Should the following code be used instead?

    • Alex

      The rule for exceptions is “throw by value, catch by (const) reference”. The compiler deals with the lifetime of the exception appropriately (generally by making a copy of the exception so that the reference points to the persisted copy instead of the anonymous object).

      • tata

        Hi, could I check by understanding by repeating/rephrasing the explanation as:

        ArrayException("Invalide index") is indeed a temporary/anonymous object, which counts as a R-value that cannot be referenced by non-const references; moreover, we have the additional problem that this object will be out of scope before reaching the catch block

        The try/catch mechanism circumvents these problems by making a copy of the anonymous/temporary object and passing instead this copy to the catch block

        Most importantly, since the copy is a L-value, one can reference it using non-const references; moreover, the copy is in scope for the catch block

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter