Is code with try-catch-rethrow equivalent to code w/o try-catch?
Asked Answered
J

6

23

Under which circumstances are the following two codes not equivalent?

{
  // some code, may throw and/or have side effects
}

try {
  // same code as above
} catch(...) {
  throw;
}

edit Just to clarify, I'm not interested in (i) deviations from above pattern (such as more code in the catch block) nor (ii) intended to invite patronising comments about the proper usage of try-catch blocks.

I'm looking for a qualified answer referring to the C++ standard. This question was prompted by a comment by Cheers and hth. - Alf to this answer of mine, stating w/o further explanation that above codes are not equivalent.


edit they are indeed different. stack un-winding will be done in the latter, but not necessarily in the former, depending on whether an exception handler (some catch block higher up the stack) is found at run time.

Josie answered 13/11, 2015 at 9:3 Comment(4)
Are there circumstances where they aren't equivalent?Natelson
@user2079303 that's exactly my question.Josie
Oh, I see. I only read the question which asks when are they not equivalent, which implied that such cases exist. I'd like to know what @Cheersandhth.-Alf is referring to.Natelson
@user2079303 So did I.Josie
P
17

The latter mandates stack unwinding, whereas in the former it is implementation-defined if the stack is unwound.

Relevant standards quotes (all from N3337):

[except.ctor]/1: As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

[except.ctor]/3: The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [...]

[except.terminate]/2: [When the exception handling mechanism cannot find a handler for a throw exception], std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. [...]

As such, if you want to guarantee that your automatic objects have their destructors run in the case of an unhandled exception (e.g. some persistent storage must be mutated on destruction) then try {/*code*/} catch (...) {throw;} will do that, but {/*code*/} will not.

Phlebotomize answered 13/11, 2015 at 9:34 Comment(6)
But how can the compiler determine whether an exception handler will ever be found, say from a calling function??Josie
@Josie I tested this across translation units using clang and the {/*code*} did not call the destructor but the rethrow version did. I don't know enough about exception implementations to say how it's done though.Phlebotomize
@Walter: It is not the compiler but the run-time system that determines whether an exception handler can be found. Since destructors called during stack unwinding cannot alter the handler (if any) that will be activated, the system is free (I think this is the meaning of [except.terminate]/2) to unwind and destruct while searching for a handler. But alternatively it might first complete the search, and only start unwinding once a handler is found. In the latter case no destruction at all will take place in the case where no handler is found.Pyongyang
@MarcvanLeeuwen thanks, that's very interesting. The problem with relying on terminate() is that the error message may get lost (or is there any guarantee otherwise). That's why I put all code inside main() in one outermost try-catch block (catching std::exception), reporting any messages (exception::what()). However, some compilers do implement code that reports that even from terminate().Josie
@Walter: The fact that the stack is not unwound in the absence of catch may be due to that the fact that the unwinding actions are linked to from the handler. In the popular (and near ubiquitous) Zero Cost exception model, all the exception handling/stack unwinding code is stashed aside by the compiler in separate code/data (which is how the Zero Cost in case of no exception is achieved), and thus to know which destructors to execute the run-time might very well need the appropriate entries there.Midden
The concrete rationale for going straight to terminate() if nothing is going to catch the exception ("two-phase unwind" in compiler jargon) is that a debugger breakpoint on std::terminate will see the stack at the point of the original throw.Rhinestone
C
7

Elaboratring on Cheers and hth. - Alf's comment:

From http://en.cppreference.com/w/cpp/error/terminate :

std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:

1) an exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case)

So stack unwinding might not happen if your

{
  // some code, may throw and/or have side effects
}

is not within another try/catch block.

Example:

struct A {
    A() {}
    ~A() { std::cout << "~A()" << std::endl; }
};

int main()
{
//    try {
        A a;
        throw 1;
//    } catch(...) {
//        throw;
//    }
}

Under coliru's gcc 5.2.0 with -O2 does not print ~A(), while with try/catch does print.

UPD: regarding your edit about separate compilation units, just tested with my local gcc 4.8.2, the behavior is the same: no stack unwinding if there is no catch. The particular example:

a.h:

struct A {
   A();
   ~A();
};

void foo();

a.cpp:

#include <iostream>
using namespace std;

struct A {
   A() {}
   ~A() { cout << "~A()" << endl; }
};

void foo() {
    A a;
    throw 1;
}

main.cpp:

#include "a.h"

int main () {
   //try {
    foo();
   //} catch(...) {
   //  throw;
   //}
}

I think that whether there is a catch is determined at run time, because anyway when exception is thrown at runtime, the program needs to look for catch. So it make sense to choose whether to unwind the stack at runtime too.

Centurial answered 13/11, 2015 at 9:43 Comment(3)
Ah, so it's different in the case where the exception is not caught at all. Can you also elaborate on a how the stack unwinding is useful when the program is going to terminate anyway?Natelson
@user2079303, obviously to do any cleanup code. Just a quick example: you have a remote server that can accept only one connection at time. You will have some Connection object in your program, and its destructor will close the connection so that the server will be ready to accept a new connection.Centurial
@user2079303, or even simpler: close a file output stream so that all data that is buffered actually hits the disk.Centurial
C
3

Semantically that is equivalent. I am not sure, if some compilers might be unable to optimize the unnevessary try-catch away. I'd prefer to leave the try-catch block out. That usually makes the code easier to unterstand.

Ceil answered 13/11, 2015 at 9:11 Comment(2)
Can you support your claim (that the codes are equivalent) with a quote form the standard?Josie
@Josie please note, that I wrote, that it is "semantically equivalent". AFAIK the standard does not state that explicitly, but it is obvious from the descriptions of how the semantics for exceptions, and exception handling.Ceil
T
3

Assuming the "some code" does not exhibit undefined behaviour (in which case all bets are off, regardless of whether you add a try/catch block or not), there will be no difference in end result. It is technically implementation defined (i.e. an implementation has to document what it does) whether stack unwinding will occur if an exception is never caught, but there has yet to be a report of any implementation that does NOT unwind the stack in such circumstances. If stack unwinding occurs, all local variables will pass out of scope, and those with destructors will have destructors invoked.

There may or may not be a measurable difference of performance, associated with the overhead of setup before the "some code" is executed, catching the exception (if any) and rethrowing, and any additional cleanup. The difference will be compiler dependent and, with old compilers, was potentially significant. With modern compilers, the difference of overhead - if any - would be somewhat less, since implementation techniques for exceptions and exception handling have improved.

Tome answered 13/11, 2015 at 9:35 Comment(5)
I'm not sure you answer my question. The compiler cannot be sure whether these codes are within a try-catch block, so how can it avoid stack unwinding???Josie
@Josie the compiler can generate code to walk (not unwind) the call stack to find an exception handler and if it doesn't find one act accordingly.Acey
@Acey your comment is the closest yet to a satisfying answer (see also my recent edit of the OP). -- could you make it into an answer?Josie
I answered your question and then you edited your post. So suggesting I haven't answered your question is misrepresentation. However, there is also nothing in the standards to stop an implementation (which technically can include a complete toolchain, not just a compiler and library) from examining your complete program and causing std::terminate() to be called and stack not unwound in circumstances where it can detect an exception is thrown and never caught. But, as I said, there are no reported cases of implementations doing that sort of thing.Tome
@Tome thanks for your answer (I have upvoted it). what you're saying in the comment is essentially what jepio said, which is interesting -- I wasn't aware of that.Josie
N
1

In case you catch the basic Exception they are completely the same. You only benefit from catching and rethrowing an exception, if you do something before throw, like logging. But you shouldn't catch Exception. Only ever catch exceptions you now how to recover from.

Newtonnext answered 13/11, 2015 at 9:15 Comment(4)
There is no "basic Exception" being caught. You're probably thinking of a feature of a language other than C++.Tome
@PalleDue No. In the case that they only catch the basic Exception it wouldn't be completely the same. The code in the question catches all thrown objects.Natelson
I was answering the question thinking it was C#. Until the edit there was no hint that it was c++. Sorry about that.Newtonnext
@PalleDue The hint is in the tags.Natelson
S
-1

Some meaningful clean up can be performed in catch block before rethrow if resources not managed as RAII idiom

       {
          // some code, may throw and/or have side effects
        }

        try {
          // same code as above
        } catch(...) {
//Some meaningful clean up can be performed here if resources not managed as RAII idiom
          throw;
        }
Sessile answered 13/11, 2015 at 9:16 Comment(3)
This does not answer the question.Josie
@Josie I just tried to point out a circumstance where these 2 code construct vary potentiallySessile
@Josie ok. Got your point! Will get back if found the answer.Sessile

© 2022 - 2024 — McMap. All rights reserved.