Concepts / Throwing and Catching Standard Exceptions

Throwing and Catching Standard Exceptions

A bare throw; inside a catch block rethrows the caught exception. The function does not return normally after the handler finishes, because the exception continues to propagate.

  • Programming
Interactive lab

Try it: try / except / else / finally

How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.

How it works

  1. Statements in try run until one raises an exception; the rest of try is skipped.
  2. Python checks the except clauses in order and runs the first one whose type matches.
  3. If nothing was raised, the else block runs instead.
  4. finally runs in every case.

Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.

Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.

Educational simulation

Loading the simulation…

Predicting Which Handler Runs

A single try block can be followed by several catch blocks, and each one names the type of exception it accepts. When something goes wrong inside the try block, the program does not simply stop or pick a handler at random. The type of the exception that was raised decides which handler receives it. Before reading the explanation, make a prediction about the situation below.

What do you think happens?

A try block has two handlers, one for std::length_error and one for std::bad_alloc. The operation inside the try block throws a std::length_error. Which handler runs, and what happens to the other handler?

  • The std::length_error handler runs, and the std::bad_alloc handler is skipped

  • The std::bad_alloc handler runs first, then the std::length_error handler

  • Both handlers run, one after the other

  • Neither handler runs because the program stops

Reveal answer
Answer:

The std::length_error handler runs, and the std::bad_alloc handler is skipped.

Each catch names the type it accepts. The thrown exception matches the std::length_error handler, so that handler receives it. The other handler does not match and is passed over.

The rest of this article traces that decision step by step. The skill it builds is the ability to say, for any failure, which handler matched and what happened next. That is the skill you need to locate the point where a program's behavior diverged from what you expected.

Matching a Thrown Exception to Its Handler

When the try block runs to completion without raising an exception, no handler runs at all. When an exception is raised, the program looks at the exception's type and selects the catch block whose declared type matches. The matching handler runs, and the other handlers attached to that try block are skipped. This is how one function can respond differently to different kinds of failure instead of treating every failure the same way.

try block runsexception raised?try block completesno handler runslength_error handlertype isstd::length_error?type isstd::bad_alloc?bad_alloc handlernoyesyesnoyes
Given a thrown exception of a specific type, which catch clause is selected, and what happens if no clause matches?

Read the diagram from top to bottom. The first question is whether anything was raised. If nothing was, the flow ends at the completed try block. If something was raised, the type is checked against each handler in turn. A match sends control into that handler. A handler that does not match is never entered for that exception. The source describes the two handlers in the example as separate cases: one labeled as a negative size, and one labeled as memory exhaustion. Each catch names a different type, so the handler that runs depends entirely on which type was raised.

Two Handlers in One Function

The code below is an illustrative sketch of the function the source describes. The source discusses the structure of the try and catch blocks and the logic of the handlers, but it does not give the full code text in this pack, so the body of the try block is a stand-in for whatever operation might fail. Focus on how the handlers are named by the exception types they accept.

cpp
Notes on the code
  • The try block holds the operation that may fail
  • The first catch accepts std::length_error, the case labeled as a negative size
  • The bare throw; rethrows the caught exception
  • The second catch accepts std::bad_alloc, the case labeled as memory exhaustion
  • std::terminate() ends the program in that second handler
Worked example

Tracing a std::length_error through test()

Assume the exception raised inside the try block is std::length_error, the case the source labels as a negative size. Trace which handler runs and what happens next.

  1. 1
    Step 1: the exception is raised

    Control leaves the normal sequence of the try block at the point of the throw, and the rest of the try block is skipped.

  2. 2
    Step 2: the handlers are checked in order

    The first catch names std::length_error, which matches the raised type, so that handler is selected.

  3. 3
    Step 3: the handler body runs

    The message is printed. The bare throw; then rethrows the same exception instead of finishing normally.

  4. 4
    Step 4: the second handler is skipped

    The std::bad_alloc handler does not match a std::length_error, so it never runs for this exception.

Result

The length_error handler runs, prints its message, and rethrows. The exception then leaves test() and continues to whatever code outside this function handles it. That outer code is not shown in the source.

Carrying Information in a Custom Exception

Definition

A custom exception class is a class you define yourself and then throw as an exception. Its purpose is to carry arbitrary information from the place where an error is detected to the place where that error can be handled. The class is not merely a label for the error. It is a container for whatever details the handler will need.

A standard exception type such as std::length_error tells a handler only what category of problem occurred. A custom class can describe the situation in more detail. The source states this general idea but does not show a full class definition, so this article does not present one. What to take away is the role: the exception object is a vehicle for information, and its class determines what that information can look like. The exception is created at the throw site, travels outward through the calls, and the handler at the catch site reads its contents.

Rethrowing with a Bare throw

Sometimes a handler does useful work and then decides that the error should continue upward rather than being resolved in the current function. The source calls this rethrowing. The statement throw; has no operand. Placed inside a catch block, it rethrows the exception that was caught, so the error propagates further. The handler does not stop the error. It performs its own work and then passes the same error onward.

outer codenot shown in sourcetest()length_error handlerthrow;rethrows the caughtexceptionrethrowpropagates outward
After a handler rethrows, where does the exception go next, and how do the calling frames get unwound until a handler is found?

The diagram captures the key change. Control does not finish normally after the catch block. The exception leaves the handler and continues on its way. Whether anything further up handles it depends on code outside this function, which the source does not show. This is why a rethrow is a decision to pass responsibility onward, not a way of making the error disappear.

Key point

A bare throw; inside a catch block rethrows the caught exception. The function does not return normally after the handler finishes, because the exception continues to propagate.

Calling std::terminate() Deliberately

Not every exception should be passed onward or recovered from in the current function. In the second handler of the rethrowing example, the code catches std::bad_alloc and calls std::terminate(). The source's reasoning is that test() is not designed to handle memory exhaustion. The function has no sensible way to continue, so the code ends the program on purpose instead of pretending the error was handled.

throw sitetest() handlersstd::terminate()1. exception thrown2. no matching handler for this type3. std::bad_alloc handler calls terminate
In what order do the throw, the failed search for a handler, and the call to std::terminate() happen, and what stops the program?

Where the Control Path Diverges

The normal path through a function runs each statement in order and reaches the end. An exception changes that path at the moment it is raised. Statements after the throw point inside the try block are skipped, and control jumps to a handler chosen by type. The table below compares what you might expect with what actually happens in the test() example.

SituationExpected pathActual path with an exception
Statements after the throw inside tryRun in orderSkipped; control leaves the try block
Which handler is consideredThe first handler listedOnly the handler whose type matches the exception
After the length_error handlerFunction finishes normallyBare throw; rethrows and the exception propagates outward
After the bad_alloc handlerFunction finishes normallystd::terminate() ends the program

Expected versus actual control flow in test()

Mistakes Beginners Make with Handlers

  • Common mistake

    Assuming every catch block runs when an exception is raised

    Why it fails

    The type of the exception decides which handler receives it. Handlers whose type does not match are skipped.

    Do this instead

    For any thrown exception, identify the one catch whose declared type matches, and trace only that handler.

  • Common mistake

    Writing throw; outside a catch block and expecting it to rethrow something

    Why it fails

    The bare throw; rethrows the exception that was caught. Without a handler in progress there is no caught exception to pass on.

    Do this instead

    Use the bare throw; only inside a catch block, where it rethrows the exception that handler received.

  • Common mistake

    Believing a rethrow means the error has been handled

    Why it fails

    A rethrow passes the same exception onward. Whatever handles it next is code outside the current function.

    Do this instead

    After a rethrow, follow the exception to the next handler up the calls and check which one matches.

  • Common mistake

    Using std::terminate() as a shortcut to avoid writing a real handler

Practice: Tracing Two Failures

MEDIUM

Using the test() function from this article, trace two cases. In the first, the exception raised is std::length_error. In the second, it is std::bad_alloc. For each case, name the handler that matched, state whether the bare throw; or std::terminate() was reached, and say what happens to the program next.

Hints
  • Check the type of the exception against each catch in order, the same way the flowchart does.
  • Only the matching handler runs. The other handler is skipped.
  • A bare throw; means the exception keeps propagating outward. std::terminate() ends the program.

Key Takeaways

  • A try block can have several catch blocks, and the exception's type decides which one runs; the other handlers are skipped.
  • A custom exception class carries arbitrary information from the point where an error is detected to the point where it is handled.
  • A bare throw; inside a catch block rethrows the caught exception so that it propagates further.
  • std::terminate() ends the program when a function is not designed to handle an exception, and it should be used deliberately.

Related concepts

More concepts starting with T