开发者

Throwing during unwinding -- why does this example work?

开发者 https://www.devze.com 2023-04-02 13:38 出处:网络
Consider this: void thrower () { throw \"123\";开发者_JS百科 } struct Catcher { ~ Catcher () { try {thrower ();}

Consider this:

void thrower () {
    throw "123";开发者_JS百科
}

struct Catcher {
    ~ Catcher () {
        try {thrower ();}
        catch (...) {}
    }
};

int main () {
    try {
       Catcher c;
       throw 1.23;
    }
    catch (...) {}
}

This compiles and runs without calling terminate on gcc 4.3, but according to the standard (15.5.1)

...when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1), calls a user function that exits via an uncaught exception... terminate shall be called.

When ~Catcher is called after the double has been thrown, this is "after completing evaluation...before the exception is caught" and thrower is "a user function that exits via an uncaught exception", this satisfies the above condition. Yes, the char* is caught but only after the user function exits.

Shouldn't terminate have been called?

To emphasise this:

void do_throw () {
    throw "123";
}

void thrower () {
    do_throw ();
    // Uncaught exception here (A)
}

struct Catcher {
    ~ Catcher () {
        try {thrower (); /* (B) */}
        catch (...) {}
    }
};

int main () {
    try {
       Catcher c;
       throw 1.23;
    }
    catch (...) {}
}

(A) happens in the context of (B), which already has an exception in progress.

So, shouldn't terminate have been called? If not, and this is a legal situation in which we can have two simultaneous exceptions, where do we draw the line?


Why should terminate have been called? You have a catch(...) block which catches all exceptions: one that catches the double (the one in main) and one that catches the char const[4] (the one in ~Catcher).

So no functions are "exiting with an uncaught exception" because all the exceptions are caught.

The key words here are "calls a user function which terminates with an uncaught exception". This is not the same as "calls a user function which somewhere down the line calls a user function which terminates with an uncaught exception." If you call a function and it has a try/catch block in it, and then in that you call some function which terminates with an exception but you catch the exception, terminate is not called.

tl;dr: The very first call in a call hierarchy originating between the exception evaluation and before the catch must exit with an exception for terminate to be called, not branches below the first call. This makes sense because there's no way you could set up another catch block between when the object being thrown was evaluated and when it was caught.


Different emphasis:

when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1), calls a user function that exits via an uncaught exception... terminate shall be called

thrower() is a user function that exits via an exception but it is not called by the exception handling mechanism; it is called by another user function (Catcher's destructor) which itself is called by the exception handling mechanism and this function doesn't exit via an exception.


Which function, called by the exception handling mechanism, exits via an uncaught exception. The only function called by the exception handling mechanism here is Catcher::~Catcher(), and it exits normally, by reaching the end of the function.

And there is fundamentally no limit to the number of simultaneously active exceptions. The limit is to the number of exceptions which must be propagated up past a given point. If Catcher::~Catcher() exited via an exception, we would have two exceptions to propagate up, the one which triggered the call of the destructor, and the one by which the destructor exited. As long as the destructor catches all exceptions, and doesn't propagate them, there is no problem.


...when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1), calls a user function that exits via an uncaught exception... terminate shall be called.

The exception handling mechanism calls ~Catcher(), which doesn't exit via an uncaught exception; hence it doesn't call std::terminate. It doesn't (directly) call thrower().


This is perfectly legal code. You're catching everything that's thrown before it causes problems. Only if you remove the try/catch in ~Catcher, you'd be throwing an exception during unwinding.

0

精彩评论

暂无评论...
验证码 换一张
取 消