share
Stack OverflowC++ Nested try-catch, tricky
[+19] [8] AlexK
[2011-11-02 14:19:07]
[ c++ exception-handling ]
[ http://stackoverflow.com/questions/7981845] [DELETED]

This question was a part of a C++ test I've taken lately. Here's a piece of code:

#include <iostream>

struct A {
    A(int value) : m_value(value) { }
    int m_value;
};

struct B : A {
    B(int value) : A (value) { }
};

int main()
{
    try {
        try {
            throw B(5);
        }
        catch(A a) {
            a.m_value *= 2;
            throw;
        }
        catch(B b) {
            b.m_value -= 2;
            throw b;
        }
    }
    catch(A a) {
        std::cout << a.m_value;
    }

    return 0;
}

Naturally, the question is, what is the output of this abomination.

If I remember correctly, the exception is getting handled by the nearest matching catch block, so the innermost throw is going to get handled by catch(A a). catch(A a) block is going to re-throw the exception. This re-throw is getting handled inside catch(B b) which in turn re-throws which invokes the outermost catch(A a) handler. So, the output is 8. However... just out of curiosity, I compiled/ran this in Visual C++ 10 as well as g++ 3.4.4. Both are unanimous in outputting 5 (handling exception in the innermost catch(A a), then re-throwing unmodified parameter then handling it in the outermost catch), which is not included in any of the multiple choice answer options. So, there are a few possibilities:

  1. The compilers are wrong (if you think this is the case, please quote the applicable part of C++ standard http://cs.nyu.edu/courses/fall11/CSCI-GA.2110-003/documents/c++2003std.pdf)

  2. I am wrong (if that's the case please also quote the document)

  3. Both are wrong, and the output should have been such and such (again if you think this is the case please quote the applicable part of the document)

Your first catch(A a) doesn't actually throw anything. - iammilind
(1) I believe your catch statements are modifying copies of B, not the thrown B. - Joe
I don't remember how it all works, but my guess is that it'll print 5. Because it's caught by catch(A), but not by reference, so it will rethrow original exception, anyway, which will be caught by other catch(A) - Michael Krelin - hacker
(2) @iammilind, what do you mean doesn't throw? It throws what it's caught with all its might. - Michael Krelin - hacker
@iammilind throw re-throws the caught exception. I expect that it will throw the original exception (with value = 5), since the catch handler caught by value rather than reference. Hmm. - RobH
(1) What i'm curious about is how can catch(A a) construct a from an object of type B without a proper constructor or assignment operation. - RedX
(2) @RedX, implicit copy constructor? - Michael Krelin - hacker
@RedX: First, the compiler defines an implicit copy constructor; second, we have slicing here. - Matthieu M.
It would be very helpful if you actually placed the code in your question. Right now, it doesn't appear where you say it does. Or at least, I can't see it. - Caelan
[+29] [2011-11-02 14:34:18] MadKeithV [ACCEPTED]

You are wrong. First of all catch(B b) is not allowed to be called. Quoth the standard:

(15.1) A handler will be invoked only by a throw-expression invoked in code executed in the handler’s try block or in functions called from the handler’s try block .

In other words, the throw in catch(A a) cannot be caught by catch(B b) because the exception is not thrown from the try-block that catch(B b) belongs to.

Secondly, the "throw" statement:

(15.1.6) A throw-expression with no operand rethrows the exception being handled. The exception is reactivated with the existing temporary; no new temporary exception object is created.

The "existing temporary" in this case is the original exception temporary thrown, not the copy being made and modified in the catch(A a) block. This behaviour is caused because the code does not catch by reference, which is always good idea anyway because that B object is being sliced by the first catch(A a) - rethrowing "a" (throw a) would no longer be a B.


(1) Thank you for the great answer. I sent an email to Brainbench (the provider of the C++ test) yesterday to bring this question to their attention. No answer so far. I guess, the fundamental question of how these language-lawyer type questions (and those are like 80% of their test) have anything to do with "predicting employee success", the Brainbench's website banner, is beyond the scope of this discussion :/ - AlexK
1
[+7] [2011-11-02 14:32:22] Mike Seymour

An exception thrown from a handler for a try block won't be caught by any other handler for the same try block; the "nearest" handler for the rethrown exception will be one from the outer try block. The relevent section of the C++03 standard is 15.1/2:

When an exception is thrown, control is transferred to the nearest handler with a matching type (15.3); “nearest” means the handler for which the compound-statement, ctor-initializer, or function-body following the try keyword was most recently entered by the thread of control and not yet exited.

Note the bit I emphasised at the end: the inner try block was exited when the first exception was thrown.

So no exception in your code is handled by the inner catch(B) block.

Also, you are catching by value, so the change you make in the catch(A) block does not affect the object that gets rethrown. throw; rethrows the same object that was thrown in the first place, not any copy that might have been made. The relevent section of the standard is 15.1/8:

The exception is reactivated with the existing temporary; no new temporary exception object is created.

So the output of "5" is correct: the inner catch(A) block modifies a copy, then rethrows the original (still with value 5) to the outer catch(A) block; if you change the catch blocks to take references, then the output will be "10".


2
[+4] [2011-11-02 14:26:56] Michael Krelin - hacker

Just decided to copy from my comment above:

I don't remember how it all works, but my guess is that it'll print 5, because it's caught by catch(A), but not by reference, so it will rethrow original exception, anyway, which will be caught by other catch(A).

(the relevant part of the document is probably page 302, 15.3-9 or something in vicinity).


3
[+4] [2011-11-02 14:39:40] James Kanze

This is a good example of why you shouldn't catch by value. (It's also a good example of why you shouldn't test for such language nits. Especially if you don't know the language that well yourself.) The compilers are correct. According to §15.3/3, the inner try will be caught by the first catch, which makes a copy of the thrown exception. (catch arguments are treated almost exactly like function arguments.) The throw in the catch block rethrows the currently handled exception (§5.1/8), not the copy passed to the catch block, so the changes to a in the catch block are ignored.

And when the compiler looks for a handler, it looks for “the handler for which the compound-statement or _ctor-initializer following the try keyword was most recently entered and not yet exited”(§15.1/2); you've exited the compound-statement following the innermost try via an exception, so the research will start at the outer try. (If it didn't, it would find the catch block you're currently in, and throw without arguments would result in an endless loop.)

(My citations of the standard are from N3291—a very recent draft. But I'm pretty sure that this hasn't changed from the official 2003 standard.)


4
[+3] [2011-11-02 14:27:02] Joe

You need to catch by reference to modify the values otherwise the result will always be the value of the original object thrown. Once caught by reference the A catch will catch the exception since it is the first match due to inheritance and your result should be 10.

    catch(A& a) {
        a.m_value *= 2;
        throw;
    }
    catch(B& b) {
        b.m_value -= 2;
        throw b;
    }

C++ catch blocks - catch exception by value or reference? [1]

[1] http://stackoverflow.com/questions/2522299/c-catch-blocks-catch-exception-by-value-or-reference

5
[+3] [2011-11-02 14:27:48] Jerry Coffin

Since the exception handlers catch the objects by value, not reference, what's seen inside the handler is a copy of the exception object, not the exception object itself. Just like in any other function, modifying that copy has no effect on the original object.


6
[+2] [2011-11-02 14:37:07] Basile Starynkevitch

For what is is worth, GCC version 4.6 gives the following warning:

% g++-4.6 -O -g -Wall testit.cc -o testit
testit.cc: In function 'int main()':
testit.cc:22:9: warning: exception of type 'B' will be caught [enabled by default]
testit.cc:18:9: warning:    by earlier handler for 'A' [enabled by default]

and running ./testitgives 5.


And so does Comeau: "ComeauTest.c", line 22: warning: handler is potentially masked by previous handler for type "A" catch(B b) { - MadKeithV
7
[+1] [2011-11-02 14:25:23] tenfour

catch(B b) { ...

This means the catch block will get its own local copy of b. Catch by reference (which is always recommended), and you will experience different behavior.


8