[cfe-dev] [LLVMdev] [LLVMDev] Object in a try-catch block not being destroyed even after an exception

John McCall rjmccall at apple.com
Tue Jun 19 19:56:23 PDT 2012


On Jun 19, 2012, at 6:25 PM, Richard Smith wrote:
> On Tue, Jun 19, 2012 at 4:48 PM, John McCall <rjmccall at apple.com> wrote:
>> On Jun 19, 2012, at 3:27 PM, Richard Smith wrote:
>>> b is constructed, since its constructor has finished.
>>> 
>>> However, the problem seems to be a bit more subtle. When we get to the
>>> destructors at the end of the full-expression, the two 'a' temporaries
>>> and 'tmp' have been constructed. In normal execution, we would then
>>> destroy the 'a' temporaries in reverse construction order, and then
>>> destroy the 'tmp' object at the end of its scope.
>>> 
>>> Now, GCC seems to believe that after the first '~a' call throws, we
>>> should destroy 'tmp' and then destroy the 'a' temporary. Perhaps it's
>>> trying to enforce reverse construction order ('tmp' was constructed
>>> after the 'a' temporaries), per 15.2/1. However, that paragraph does
>>> not directly apply, since temporaries don't have automatic storage
>>> duration (see core issue 365).
>> 
>> I don't find the reasoning in this core issue convincing, and I agree
>> with Steve Adamczyk's comment that it's really just their object lifetime
>> that's a bit odd, not their storage duration.
> 
> The problem, as I see it, is that the standard currently specifies
> that they have static storage duration (because they are objects
> "implicitly created by the implementation" (3.7/2), but they're not
> "block-scope variables" (3.7.3/1)).

The choice of wording in [basic.stc] is not accidental:  a temporary is an
object but not a variable.  I do not believe [basic.stc.auto] is intended to be
exhaustive.

[class.temporary]p5 strongly suggests (but no, it does not state directly) that
the storage duration of a temporary varies by how it's used, e.g. being
increased when bound as the initializer of a reference of static duration.
Note the places which discuss objects "with the same storage duration" as
the temporary.

As an aside, the standard does not seem to require us to destroy a variable
of *static* storage duration if the destructor of a temporary required during
initialization throws — and yet I believe it does require us to repeat
initialization in this case.  So really there's quite a bit of poor drafting in
this area.

>> I believe the intent of the standard is quite clearly to have the local
>> variable destroyed in reverse order of construction w.r.t. the temporaries,
>> much like a return value must be.
> 
> That does not match my expectation, which is that in:
> 
>  S s(T(), T());
> 
> the T temporaries should *always* be destroyed before the S object is.
> Having an inconsistency in destruction order (depending on whether one
> of the ~T() calls throws) does not seem useful, and I don't see any
> way to read the standard in which it is mandated. Indeed, consider the
> following (credit to Chandler for the argument, any flaws in conveying
> it are my own):
> 
> * The destructors for local variables are only invoked "As control
> passes from a throw-expression to a handler".
> * The evaluation of the throw-expression's full-expression is
> sequenced-before the evaluation of the handler.
> * Therefore the throw-expression and its temporaries are destroyed
> before control passes.
> * Therefore the temporaries are destroyed before the objects of
> automatic storage duration are.

Er.  I am reading your argument as:
  end-throw-expression < begin-passing-control < destroy-locals < end-passing-control < begin-handler
  end-throw-expression < begin-handler
  (implicitly) destroy-throw-expression-temporaries < end-throw-expression
therefore
  end-throw-expression < begin-passing-control
  destroy-throw-expression-temporaries < begin-passing-control
  destroy-throw-expression-temporaries < destroy-locals

This is all nicely tautological, but it doesn't seem to argue for anything,
and it certainly doesn't contradict the proposition that, during unwind,
local objects are always destroyed in the reverse of the order in which
they were constructed.

It is also somewhat irrelevant because you cannot have a throw lexically
in the full-expression for an initializer and have that throw evaluated
after the completion of the variable's constructor.

Also, your premises are wrong, because the operand of a
throw-expression is not itself a full-expression, so the destruction of
"its temporaries" does not occur until unwinding begins, i.e. during
the passing of control from the throw-expression to the handler.
We are then clearly controlled by [class.temporary]p3, and [except.ctor]p3
(terminate on throw) applies.

> I would also like to put forward the hypothesis that the change
> suggested would, in practice, only ever break code, because almost all
> code is going to be set up to correctly handle the case where the
> temporaries are destroyed before the local variable. Hence I'd like to
> understand the motivation for GCC's behavior, in case there are cases
> where it is important to destroy the T temporary after the S object is
> destroyed.


This is equally true of return values:  along the normal path, all the local
variables will be destroyed before the return value is, and yet the unwind
path is necessarily different.

John.



More information about the cfe-dev mailing list