[rfc] fix libstdc++/10606
Martin Sebor
sebor@roguewave.com
Tue Jan 4 23:45:00 GMT 2005
Richard Henderson wrote:
> On Tue, Jan 04, 2005 at 03:52:18PM -0600, Benjamin Kosnik wrote:
>
>>+ {
>>+ S s0;
>>+ throw s0; // s1 is the exception object
>>
>>we want
>>
>>+ { 1, 0, false },
>>
>>for the s1 temporary object test data. I think this is correct because
>>this test occurs in the temporary object's ctor body, before the the
>>object is considered complete. I don't think it makes sense to have
>>uncaught_exception return true here, since the "uncaught exception
>>object" in question hasn't yet been (fully) constructed.
>
>
> On the other hand, we *will* std::terminate if the s0->s1 copy
> constructor throws -- just like if the s1->s2 copy constructor
> throws while setting up the catch handler.
>
> Given that the copy constructor cannot throw, I think it makes
> sense to consider that we've begun processing the exception,
> and thus there must be an uncaught exception.
>
> It all depends on what you consider uncaught_exception useful
> for, I guess.
When uncaught_exception() returns true, throwing an exception may
(but isn't required to) cause the runtime to call terminate().
When it returns false, throwing an exception is guaranteed not
to cause a call to terminate (provided there's a handler for it,
of course). This is useful in implementing the copy ctors of
exception classes.
From the table in core issue 475, requiring "0|1|1|1" as the
proposed resolution suggests, means that either of "0|1|1|1|OK"
and "0|1|1|1|ABRT" is permitted. Requiring "0|0|1|1" (which would
be my preference) would mean that only "0|0|1|1|OK" would be
allowed (i.e., no call to terminate() could happen during the
construction of the [temporary] object to be thrown).
Suppose "0|1|1|1" is required and you are implementing the copy
ctor of an exception class. You'd have to code it along these
lines for exception safety:
struct B { /* third party non-exception class */ };
struct M { /* third party non-exception class */ };
struct S: B {
M m;
S (const S &rhs)
try: B (rhs), m (rhs.m) { /* ... */ }
catch (...) {
if (std::uncaught_exception ()) {
// what to do here?
}
else
throw;
}
};
try {
throw S (); // can only throw S or call terminate()
}
catch (const S &s) { /* cannot throw */ }
catch (...) { /* never reached */ }
and hope that B's or M's copy ctor won't throw while S is being
thrown. If they did there isn't much you could do except terminate
your program (or let the exception escape and risk that the runtime
might call terminate()).
If, on the other hand, "0|0|1|1" were required, the copy ctor wouldn't
have to worry about exceptions and could simply let them propagate to
the caller. If an exception occurred while S was being thrown the new
exception would simply displace S. A defensively written program won't
catch exceptions by value so S won't have to worry about terminate()
being called while the handler's exception object is being initialized.
The code would look like this:
struct S: B {
M m;
S (const S &rhs): B (rhs), m (rhs.m) { /* ... */ }
};
try {
throw S (); // can throw S or anything else
}
catch (const S &s) { /* cannot throw */ }
catch (...) { /* handle exceptions thrown from S::S(const S&) */ }
Martin
Here's the issue for reference:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#475
More information about the Libstdc++
mailing list