20.3 — Exceptions, functions, and stack unwinding

In the previous lesson on basic exception handling, we explained how throw, try, and catch work together to enable exception handling. In this lesson, we’ll talk about how exception handling interacts with functions.

Throwing exceptions outside of a try block

In the examples in the previous lesson, the throw statements were placed directly within a try block. If this were a necessity, exception handling would be of limited use.

One of the most useful properties of exception handling is that the throw statements do NOT have to be placed directly inside a try block due to the way exceptions propagate up the stack when thrown. This allows us to use exception handling in a much more modular fashion. We’ll demonstrate this by rewriting the square root program from the previous lesson to use a modular function.

In this program, we’ve taken the code that checks for an exception and calculates the square root and put it inside a modular function called mySqrt(). We’ve then called this mySqrt() function from inside a try block. Let’s verify that it still works as expected:

Enter a number: -4
Error: Can not take sqrt of negative number

It does!

Let’s revisit for a moment what happens when an exception is raised. First, the program looks to see if the exception can be handled immediately (which means it was thrown inside a try block). If not, the current function is terminated, and the program checks to see if the function’s caller will handle the exception. If not, it terminates the caller and checks the caller’s caller. Each function is terminated in sequence until a handler for the exception is found, or until main() is terminated without the exception being handled. This process is called unwinding the stack (see the lesson on the stack and the heap if you need a refresher on what the call stack is).

Now, let’s take a detailed look at how that applies to this program when an exception is raised from within mySqrt(). First, the program checks to see if the exception was thrown from within a try block inside the function. In this case, it was not. Then, the stack begins to unwind. First, mySqrt() terminates, and control returns to main(). The program now checks to see if we’re inside a try block. We are, and there’s a const char* handler, so the exception is handled by the try block within main().

To summarize, mySqrt() raised the exception, but the try/catch block in main() was the one who captured and handled the exception. Or, put another way, try blocks catch exceptions not only from statements within the try block, but also from functions that are called within the try block.

The most interesting part of the above program is that the mySqrt() function can throw an exception, but this exception is not immediately inside of a try block! This essentially means mySqrt is willing to say, “Hey, there’s a problem!”, but is unwilling to handle the problem itself. It is, in essence, delegating the responsibility for handling the exception to its caller (the equivalent of how using a return code passes the responsibility of handling an error back to a function’s caller).

At this point, some of you are probably wondering why it’s a good idea to pass errors back to the caller. Why not just make MySqrt() handle its own error? The problem is that different applications may want to handle errors in different ways. A console application may want to print a text message. A windows application may want to pop up an error dialog. In one application, this may be a fatal error, and in another application it may not be. By passing the error back up the stack, each application can handle an error from mySqrt() in a way that is the most context appropriate for it! Ultimately, this keeps mySqrt() as modular as possible, and the error handling can be placed in the less-modular parts of the code.

Another stack unwinding example

Here’s another example showing stack unwinding in practice, using a larger stack. Although this program is long, it’s pretty simple: main() calls first(), first() calls second(), second() calls third(), third() calls last(), and last() throws an exception.

Take a look at this program in more detail, and see if you can figure out what gets printed and what doesn’t when it is run. The answer follows:

Start main
Start first
Start second
Start third
Start last
last throwing int exception
first caught int exception
End first
End main

Let’s examine what happens in this case. The printing of all the “Start” statements is straightforward and doesn’t warrant further explanation. Function last() prints “last throwing int exception” and then throws an int exception. This is where things start to get interesting.

Because last() doesn’t handle the exception itself, the stack begins to unwind. Function last() terminates immediately and control returns to the caller, which is third().

Function third() doesn’t handle any exceptions, so it terminates immediately and control returns to second().

Function second() has a try block, and the call to third() is within it, so the program attempts to match the exception with an appropriate catch block. However, there are no handlers for exceptions of type int here, so second() terminates immediately and control returns to first(). Note that the integer exception is not implicitly converted to match the catch block handling a double.

Function first() also has a try block, and the call to second() is within it, so the program looks to see if there is a catch handler for int exceptions. There is! Consequently, first() handles the exception, and prints “first caught int exception”.

Because the exception has now been handled, control continues normally at the end of the catch block within first(). This means first() prints “End first” and then terminates normally.

Control returns to main(). Although main() has an exception handler for int, our exception has already been handled by first(), so the catch block within main() does not get executed. main() simply prints “End main” and then terminates normally.

There are quite a few interesting principles illustrated by this program:

First, the immediate caller of a function that throws an exception doesn’t have to handle the exception if it doesn’t want to. In this case, third() didn’t handle the exception thrown by last(). It delegated that responsibility to one of its callers up the stack.

Second, if a try block doesn’t have a catch handler for the type of exception being thrown, stack unwinding occurs just as if there were no try block at all. In this case, second() didn’t handle the exception either because it didn’t have the right kind of catch block.

Third, once an exception is handled, control flow proceeds as normal starting from the end of the catch blocks. This was demonstrated by first() handling the error and then terminating normally. By the time the program got back to main(), the exception had been thrown and handled already -- main() had no idea there even was an exception at all!

As you can see, stack unwinding provides us with some very useful behavior -- if a function does not want to handle an exception, it doesn’t have to. The exception will propagate up the stack until it finds someone who will! This allows us to decide where in the call stack is the most appropriate place to handle any errors that may occur.

In the next lesson, we’ll take a look at what happens when you don’t capture an exception, and a method to prevent that from happening.

20.4 -- Uncaught exceptions and catch-all handlers
20.2 -- Basic exception handling

45 comments to 20.3 — Exceptions, functions, and stack unwinding

  • sito

    I think exception handling in c++ is really badly implemented. Let's say for example you have a function that allocates resources on the heep. If you allocate something on the heep you need a destructor in order to deallocate it. Now when you throw an error in a function and you capture that error the function won't have returned a value and no deallocation is performed. stack unwinding and exceptions seems very stupid in c++ because it's so unsafe and if you have a large project with allot of exceptions performence is going to take a hit.
    SO with these points in mind is there any reason why to use exceptions in c++?

    • Alex

      > Now when you throw an error in a function and you capture that error the function won't have returned a value and no deallocation is performed

      This is generally why you shouldn't dynamically allocate resources on the heap in a non-member function. It's not a problem exclusive to exceptions -- it can also happen if you function early returns, or if you call std::exit().

      If you always only allocate dynamic resources inside a class (e.g. using std::unique_ptr), then (outside of the std::exit() use case) you're guaranteed the object will be destroyed even if the function returns early or throws an exception.

  • Alireza Nikpay

    Hi Alex.
    When mySqrt throw exception, in catch block we can use the exception, is it means that const char* message in mySqrt ("Can not ...") is global when thrown?
    Also lifetime of that const char* is over when catch block execute?

    • Alex

      C-style string literals have static storage duration, and thus exist for the lifetime of the program.

      • Alireza Nikpay

        Thanks for response.
        But i mean how we can get exception value, not just C-style string.

        Something like this.
        My guess is T will extend his lifespan for catch clause. Am I right?

  • yeokaiwei

    1. Feedback
    Hi Alex,
    There will be an unreacheable code error C4702 with Microsoft Visual Studio 2019.

    Edit: Oh, it was actually a quiz to see what wouldn't was unreachable.

    "void last() // called by third()
        std::cout << "Start last\n";
        std::cout << "last throwing int exception\n";
        throw - 1;
        std::cout << "End last\n"; <-----C4702 error


  • Glenn

    "By passing the error back up the stack..."

    Should this read 'down'?  Because the called function is always 'up-stack' from its caller, yes?

  • ZeQuan

    I love these lessons

  • boltzmann

    Here's an example of try-catch code which works but does not correspond to any of your examples.  Could you please explain why?

  • Yosry

    Hey Alex,
    I am not sure if you check the comments here anymore, anyways, why didn't the catch in second() handle this exception? I mean it has a catch that takes a double as its parameter and as I believe -1 should be implicitly casted to double? Why didn't this happen here?

    • Alex

      Exceptions don't perform implicit conversions for catch blocks. I added a note about such into both this lesson and the previous one since this isn't obvious.

  • Lamont Peterson


    The output of the first example in this lesson is not what the lesson suggests, but rather:

    This is what I expected to see when I was putting the code together, and it makes sense.

  • Narendra

    Hi Alex,

    small point line 46 of second example . It needs std:

    Your tutorials are awesome. I would like to thank over million of times :))


  • Martin

    Hi Alex,
    I have a question about line 22 in the first example. Why is the expression not evaluated from left to right but instead the function mySqrt() is executed before the streaming operator <<. Shouldn't the example first print "The square root of x is" and then throw the exception?
    Thanks for your help.

    • Alex

      Function calls (operator()) have higher precedence than operator<<, so it evaluates first. See lesson 3.1 for the table on precedence.

      • Craig Scratchley

        I know about precedence, but I have concerns about the order of evaluation.

        For example, if we had the expression
        1 + 2 + 4*5
        then we know that 4*5 is evaluated before 3 + 20, but I think it is certainly fine for 1 + 2 to be evaluated before 4*5

        Your code in question is similar but just using different operators and precedence levels.

        If I am wrong, can you point me to the language rules governing order of evaluation that specifies that I am wrong.  Thanks.  By the way, thank-you so much for making these web pages available.  Your coverage of exceptions is better than in many textbooks and other resources.

        • Alex

          I discuss both precedence and associativity in lesson 3.1.

          C++ will evaluate 4*5 first, and then 1 + 2 + 20 from left to right, per the associativity and precedence rules laid out in the table in lesson 3.1.

          • Craig Scratchley

            I believe you may be wrong about that. states:

            "Order of evaluation of the operands of almost all C++ operators (including [...] the order of evaluation of the subexpressions within any expression) is unspecified. The compiler can evaluate operands in any order, and may choose another order when the same expression is evaluated again."

            So in my example the second addition operator ends up with operands (1 + 2) and 3*4.  The above states that the order of evaluation of these operands is unspecified.  With your insertion operators you might be lucky on some compilers and options for those compilers (like optimization options or lack thereof), but it seems you can't make your general statement on order of evaluation.

            The associativity does say that the first addition operator is evaluated before the second addition operator, and precedence tells us that 3*4 will be an operand of the second addition operator, but it seems we can't specify the order of evaluation for the two operands of that second addition operator.

            If I am indeed wrong, please point me to the official language rules supporting your assertion.

            • Craig Scratchley

              Oops.  In two locations above, 3*4 should be 4*5.  Sorry, I had started on paper with 1 + 2 + 3*4, and then later switched the 3*4 to be 4*5.  Hopefully Alex can make the correction above and delete this "oops" reply.

            • Alex

              First, let's differentiate evaluation vs. association. When we call a function like "someFunc(a(), b())", arguments a() and b() are evaluated to produce a value that is passed to the function. C++ does not specify an order of evaluation for function arguments. Association tells us how things cluster, so a+b+c evaluates as (a+b)+c.

              Now we also know that operators are implemented as functions, so a+b evaluates as operator+(a, b). If a and b are literals, there's no evaluation necessary. If a and b are variables or function calls or expressions, those get evaluated to a value.

              Due to association we know that 1+2+3*4 will evaluate as (1+2)+(3*4). If the compiler treats this as operator+(operator+(1,2),operator*(3,4)), then I'd agree with you, operator+(1,2) or operator*(3,4) could be evaluated first. But that would be ignoring precedence rules, which state that 3*4 should be evaluated first. So I suspect that the compiler will simply evaluate operator*(3,4) first (since it's of higher precedence), and then evaluate the subexpression ((1+2)+12). But I can't find anything definitive to indicate that this is actually so.

              • Alex

                Just following up on this, I was curious, so I wrote a class called Test and overloaded operator+ and operator* to do nothing but print out "+" and "*" respectively.

                This printed: *++*++

                Which indicates that with Visual Studio 2017, z * w is evaluated first regardless of whether it's on the right or left side of the expression. If the compiler was generated nested function calls that could be evaluated in either order, I'd expect to see either *++++* (right argument evaluated first) or ++**++ (left argument evaluated first).

                I think what the page you linked is trying to say is that when we have a compound expression like a() + b() + c(), the function calls to a, b, or c might be evaluated in any order, but the actual addition will always happen deterministicly due to the association rules.

                • Craig Scratchley

                  You wrote: "but the actual addition will always happen deterministicly due to the association rules"

                  There is no argument about the addition, it should be evaluated left to right according to the C/C++ standards.  You are proposing that for the 2nd addition in a particular case, that the addend is evaluated before the augend, and, as I understand, the C++ standard does not dictate this.

                  It certainly should be possible for a C++ compiler to use reverse-descent parsing in order to generate an Abstract Syntax Tree (AST), and, with a reasonable grammar definition for C++, that could give us the following subtree in an AST for a program including the expression   1 + 2 + 4*5:

                      /    |
                     +     *
                    / |     | |
                  1  2     4  5

                  A reasonable evaluation of this subtree would evaluate 1 + 2 before evaluating 4*5.

                  This can actually save memory if compiler is trying to optimize.  Consider an expression like:

                  a*b + c*d + e*f + h*i + j*k + l*m + n*o;

                  It would save space to build up the sum incrementally and throw away the products as soon as they can be folded into the developing sum.  That is what a reverse-descent parser with a reasonable grammar would do.  In any case, the compiler should be allowed to optimize for space in this fashion if it wanted to.  It does not violate rules of precedence or association.  Precedence does not state that all multiplications need to be completed before any additions are started.

                  Again, if I am wrong, please point me to the section of the standard that contradicts my statements.

                  As for the example program near the top of this section of your chapter, if you put mySqrt(x) in the try block before you do any output in that try block, then the program will behave the way you are expecting no matter how any compiler may handle the code.

                  Lastly, I should point out that I received an email for your 8:41 pm reply to me, but not for your 10:25 pm reply to yourself.  Therefore I didn't know that you had further extended the thread until I happened to check it today.  Thanks again for your work.  This is a useful resource to have available.

                  • Alex

                    What you say makes sense logically, but I can't find anything deterministic either way. This would be a good question for Stack Overflow -- perhaps some people who have worked on compilers could tell you whether C++ allows that level of flexibility or not.

                    I'm aware that the email notifier isn't reliable, but for the life of me I can't figure out why. I'm still looking into it.

  • Chris

    Hey Alex.
    What's the benefit of using std::cerr over std::cout in the catch blocks to write an error description?


  • Mauricio Mirabetti

    Alex, a few typos:

    main() calls Ffirst() -> first()
    void Second() // called by first() -> second()
    Function last() prints “Last throwing int exception” and then throws an int exception. -> "last ...
    #include "math.h" // for sqrt() function -> not a typo, but cmath would be more recent

    Best regards!

  • Yueqing Zhang


  • Jie

    Hi Alex, Can I ask you a question not directly related to the tutorial? I have followed your tutorials up to this part now, it help me so much in understanding basics about C++. But I kind of don't know where to go next, once I finish your tutorial...
    So I'm wondering if you know any good websites or books where I can find some projects to actually apply all I have learned here, or just to learn more things in standard libraries in depth. again, Thank you so much for this tutorial! Any answer will be really appreciated~

    • Alex

      My recommendations for things to do next:
      1) Learn about data structures and algorithms
      2) Learn about all the functionality in the C++ standard library, and when it's appropriate to use
      3) Learn about the basics of graphical user interfaces and event-driven programming

      I don't have any good recommendation for books or websites for these but I'm sure a Google search will turn up other tutorials.

      • Jie

        Thank you for the reply! I just started reading Robert Sedgewick's book in C++. It is not as succinct and organized as this tutorial, but I will try to learn as much as possible. Thanks again for this great tutorial~ I guess I will come back now and then to review things:)

  • Ananya

    Hi Alex, Thanks for the material. very nicely written Good work :-)

  • Sunder

    Can I use exception inside the Constructor during the object creation to ensure it is created or not. If yes then please let me know how?

  • Minhyme

    After compiling the modular square root function, as well as another of my own basic programs, and inputting a negative numbers the console gives me this error:

    "This application has requested the Runtime to terminate it in an unusual way.
    Please contact the application's support team for more information."

    The program then crashes.
    Throwing and catching integers seems to work fine but strings crash the program.
    How can I fix this?

  • Madhu

    If Third() function has the exception handler then in this case how the stack unwinding will happen.

    • Alice

      because the Third() function only handles exception in the type of double while the exception thrown by last is an int.

    • Alex

      In that case, third() would handle the exception, and then control would return to normal. Function third() would print "End third", and return to second(), which would print "End second" and return to first, and so on.

  • Daniel

    Thanks for writing these, you always explain things very clearly. Maybe you could make reference to the std exception libary and throwing classes.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]