Topic: Behavior of array deletion if an element's dtor throws
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Fri, 30 Apr 2010 15:24:36 CST Raw View
I've asked a lot of questions about C++0x recently, so let me emphasize that I'm
asking about current C++ (C++03) here.
Consider:
Widget *pwa = new Widget[100];
...
delete [] pwa;
Suppose that the destructor for pwa[50] throws. What happens next? 15.1
tells me that "control is transferred to the nearest handler with a
matching type," but 5.3.5/6 tells me that in a delete expression for an
array, "the elements will be destroyed in order of decreasing address (that
is, in reverse order of the completion of their constructor." So are
destructors invoked for array elements 0-49?
My sense is that when pwa[50]'s destructor throws, the delete expression is
essentially abandoned (much as a looping expression would be abandoned if
an exception occurred during iteration), elements 0-49 of the array are not
destroyed, and the memory occupied by the array is not reclaimed by
operator delete (because that would have been performed by the
now-abandoned part of the delete expression), but I'd like to have more to
go on than how I sense things. So is the behavior of the above spelled out
by the standard?
Secondarily, is the behavior specified by C++0x any different?
Thanks,
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near
Seattle (http://cppandbeyond.com/)
* License my training materials for commercial
(http://tinyurl.com/yfzvkp9) or personal use
(http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Sun, 2 May 2010 14:20:47 CST Raw View
On 30 Apr., 23:24, Scott Meyers <NeverR...@aristeia.com> wrote:
> I've asked a lot of questions about C++0x recently, so let me emphasize that I'm
> asking about current C++ (C++03) here.
>
> Consider:
>
> Widget *pwa = new Widget[100];
> ...
> delete [] pwa;
>
> Suppose that the destructor for pwa[50] throws. What happens next? 15.1
> tells me that "control is transferred to the nearest handler with a
> matching type," but 5.3.5/6 tells me that in a delete expression for an
> array, "the elements will be destroyed in order of decreasing address (that
> is, in reverse order of the completion of their constructor." So are
> destructors invoked for array elements 0-49?
Yes, an implementation is required to destroy the remaining elements
of the array, even if an exception occurs as of 5.3.5/6. This rule
also
applies to situations where no heap memory management is involved
(e.g. for an automatic array object), see [except.ctor]/2:
"An object that is partially constructed [..] will have destructors
executed
for all of its [...] subobjects for which the constructor has
completed
execution and the destructor has not yet begun execution."
The wording is similar for C++0x, but additionally honors the
situation
related to the new language feature of delegating constructors.
If I say "the language requires" above, this does not mean that the
remaining rules of the language are invalidated. If for example more
than one destructor of such an array would throw an exception, this
would lead to an invocation of std::terminate as of [except.terminate]/
1
bullet 1.
> My sense is that when pwa[50]'s destructor throws, the delete expression is
> essentially abandoned (much as a looping expression would be abandoned if
> an exception occurred during iteration), elements 0-49 of the array are not
> destroyed, and the memory occupied by the array is not reclaimed by
> operator delete (because that would have been performed by the
> now-abandoned part of the delete expression), but I'd like to have more to
> go on than how I sense things. So is the behavior of the above spelled out
> by the standard?
The requirements mentioned in [expr.delete] and [except.ctor] are
binding and an conforming implementation has to honor them. In
regard to the call of the deallocation function, this is also
required,
see [expr.delete]/7:
"The delete-expression will call a deallocation function (3.7.3.2)."
> Secondarily, is the behavior specified by C++0x any different?
No, but there existed some uncertainty about the stringency
of [expr.delete]/7, therefore as part of core issue
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#353
a note was added during CD1 to make the intention clearer:
"[ Note: The deallocation function is called regardless of
whether the destructor for the object or some element of
the array throws an exception. end note ]"
HTH & Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Sun, 2 May 2010 22:16:12 CST Raw View
Daniel Kr=FCgler wrote:
> On 30 Apr., 23:24, Scott Meyers <NeverR...@aristeia.com> wrote:
>> Suppose that the destructor for pwa[50] throws. What happens next? 15.=
1
>> tells me that "control is transferred to the nearest handler with a
>> matching type," but 5.3.5/6 tells me that in a delete expression for an
>> array, "the elements will be destroyed in order of decreasing address (t=
hat
>> is, in reverse order of the completion of their constructor." So are
>> destructors invoked for array elements 0-49?
>
> Yes, an implementation is required to destroy the remaining elements
> of the array, even if an exception occurs as of 5.3.5/6.
>> My sense is that when pwa[50]'s destructor throws, the delete expression=
is
>> essentially abandoned (much as a looping expression would be abandoned i=
f
>> an exception occurred during iteration), elements 0-49 of the array are =
not
>> destroyed, and the memory occupied by the array is not reclaimed by
>> operator delete (because that would have been performed by the
>> now-abandoned part of the delete expression), but I'd like to have more =
to
>> go on than how I sense things. So is the behavior of the above spelled =
out
>> by the standard?
>
> The requirements mentioned in [expr.delete] and [except.ctor] are
> binding and an conforming implementation has to honor them. In
> regard to the call of the deallocation function, this is also
> required,
> see [expr.delete]/7:
But presumably 15.1 is binding, too. What in the standard says that the
behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to b=
e
abandoned? Or (equivalently), what in the standard says that the behavior
dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run t=
o
completion?
If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
terminate when an exception is thrown while another exception has been thro=
wn
but not caught) also deferred?
Informally (and I realize that using that word in this kind of a discussion=
in
this newsgroup puts me on very shaky ground), we say that when an exception=
is
thrown, normal execution stops, and control is transferred to a handler (if
there is one). When a throw expression executes, we stop executing looping
constructs, jump out of blocks, etc. 3.5.3/6 is essentially dictating that
compilers generate a looping construct. If we wrote the loop ourselves, we=
'd
stop executing it and propagate an exception (absent a catch block inside t=
he
loop). Where does the standard say that the compiler-generated loop doesn'=
t do
the same thing?
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near Seattle
(http://cppandbeyond.com/)
* License my training materials for commercial (http://tinyurl.com/yfzvkp9)=
or
personal use (http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std-c++@netlab.cs.rpi.edu <std-c%2B%2B@netlab.cs.rpi.edu>]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Mon, 3 May 2010 13:09:01 CST Raw View
On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> Daniel Kr=FCgler wrote:
> > The requirements mentioned in [expr.delete] and [except.ctor] are
> > binding and an conforming implementation has to honor them. In
> > regard to the call of the deallocation function, this is also
> > required, see [expr.delete]/7:
>
> But presumably 15.1 is binding, too. What in the standard says that the
> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
be
> abandoned? Or (equivalently), what in the standard says that the behavior
> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
to
> completion?
Yes, but 15.1 does *not* require abandoning 5.3.5.
> If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
> terminate when an exception is thrown while another exception has been
> thrown but not caught) also deferred?
Evidently, the implementation's delete [] must have a catch handler
that
completes 5.3.5, then rethrows the exception. 15.5.1 isn't deferred
because
the second exception happens within a catch handler.
> Informally (and I realize that using that word in this kind of a
discussion in
> this newsgroup puts me on very shaky ground), we say that when an
exception
> is thrown, normal execution stops, and control is transferred to a handler
(if
> there is one). When a throw expression executes, we stop executing
looping
> constructs, jump out of blocks, etc. 3.5.3/6 is essentially dictating
that
> compilers generate a looping construct.
Agreed.
> If we wrote the loop ourselves, we'd
> stop executing it and propagate an exception (absent a catch block inside
> the loop). Where does the standard say that the compiler-generated loop
> doesn't do the same thing?
Or a catch block containing the loop, that tries to complete the loop.
template<class T>
void mock_delete_array_op_throwing_destructor(T* p, size_t n)
{
try {
while(0<n) p[--n]->~T();
/*
* If object file format doesn't provide the handwavium,
* the implementation is automatically non-compliant.
* I'd love to be wrong, but it's very unclear where this information
* would reside in any of OMF, COFF, or ELF object formats
* without vendor extensions.
*/
catch(/* handwavium: exception type thrown by T::~T */& e)
{
while(0<n) p[--n]->~T();
throw;
}
/*
* more catch clauses, as above, until all exceptions throwable
* by T::~T are caught
*/
}
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Tue, 4 May 2010 13:18:48 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
> On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>> Daniel Kr=FCgler wrote:
>
>>> The requirements mentioned in [expr.delete] and [except.ctor] are
>>> binding and an conforming implementation has to honor them. In
>>> regard to the call of the deallocation function, this is also
>>> required, see [expr.delete]/7:
>> But presumably 15.1 is binding, too. What in the standard says that the
>> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
> be
>> abandoned? Or (equivalently), what in the standard says that the behavior
>> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
> to
>> completion?
>
> Yes, but 15.1 does *not* require abandoning 5.3.5.
>
>> If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
>> terminate when an exception is thrown while another exception has been
>> thrown but not caught) also deferred?
>
> Evidently, the implementation's delete [] must have a catch handler
> that
> completes 5.3.5, then rethrows the exception.
What is the part of the standard that makes this evident?
> 15.5.1 isn't deferred
> because
> the second exception happens within a catch handler.
Where does 15.5.1 require abandoning 5.3.5? 15.5.1 simply says that terminate
is called. It does not say that terminate is "immediately" called.
It does not
say that whatever operation you're in the middle of shall be abandoned. It
uses, from my reading, the same kind of language as 15.1, which says that "when
an exception is thrown, control is transferred to the nearest handler." So why
should we conclude that the behavior dictated by 5.3.5 is continued when 15.1
applies, but the behavior dicated by 5.3.5 is not continued when 15.5.1 applies?
> Or a catch block containing the loop, that tries to complete the loop.
Yes, but where does the standard say that this is the proper behavior?
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near Seattle
(http://cppandbeyond.com/)
* License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
personal use (http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std-c++@netlab.cs.rpi.edu <std-c%2B%2B@netlab.cs.rpi.edu>]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Martin B." <0xCDCDCDCD@gmx.at>
Date: Tue, 4 May 2010 13:29:31 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
> On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>
>> Daniel Kr=FCgler wrote:
>>
>
> The requirements mentioned in [expr.delete] and [except.ctor] are
>>> binding and an conforming implementation has to honor them. In
>>> regard to the call of the deallocation function, this is also
>>> required, see [expr.delete]/7:
>>>
>> But presumably 15.1 is binding, too. What in the standard says that the
>> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
>>
> be
>
>> abandoned? Or (equivalently), what in the standard says that the behavior
>> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
>>
> to
>
>> completion?
>>
>
> Yes, but 15.1 does *not* require abandoning 5.3.5.
>
> If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
>> terminate when an exception is thrown while another exception has been
>> thrown but not caught) also deferred?
>>
>
> Evidently, the implementation's delete [] must have a catch handler
> that
> completes 5.3.5, then rethrows the exception. 15.5.1 isn't deferred
> because
> the second exception happens within a catch handler.
>
> Informally (and I realize that using that word in this kind of a
>>
> discussion in
>
>> this newsgroup puts me on very shaky ground), we say that when an
>>
> exception
>
>> is thrown, normal execution stops, and control is transferred to a handler
>>
> (if
>
>> there is one). When a throw expression executes, we stop executing
>>
> looping
>
>> constructs, jump out of blocks, etc. 3.5.3/6 is essentially dictating
>>
> that
>
>> compilers generate a looping construct.
>>
>
> Agreed.
>
> If we wrote the loop ourselves, we'd
>> stop executing it and propagate an exception (absent a catch block inside
>> the loop). Where does the standard say that the compiler-generated loop
>> doesn't do the same thing?
>>
>
> Or a catch block containing the loop, that tries to complete the loop.
>
> template<class T>
> void mock_delete_array_op_throwing_destructor(T* p, size_t n)
> {
> try {
> while(0<n) p[--n]->~T();
> /*
> * If object file format doesn't provide the handwavium,
> * the implementation is automatically non-compliant.
> * I'd love to be wrong, but it's very unclear where this information
> * would reside in any of OMF, COFF, or ELF object formats
> * without vendor extensions.
> */
> catch(/* handwavium: exception type thrown by T::~T */& e)
> {
> while(0<n) p[--n]->~T();
> throw;
> }
> /*
> * more catch clauses, as above, until all exceptions throwable
> * by T::~T are caught
> */
> }
>
>
What's wrong with "catch(...)"?
try {
while(0<n) p[--n]->~T();
}
catch(...) {
try {
while(0<n) p[--n]->~T();
}
catch(...) {
abort();
}
throw;
}
cheers,
Martin
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Martin B." <0xCDCDCDCD@gmx.at>
Date: Tue, 4 May 2010 22:25:54 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
>
> On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>>
>> Daniel Kr=FCgler wrote:
>
>>> The requirements mentioned in [expr.delete] and [except.ctor] are
>>> binding and an conforming implementation has to honor them. In
>>> regard to the call of the deallocation function, this is also
>>> required, see [expr.delete]/7:
>>
>> But presumably 15.1 is binding, too. What in the standard says that the
>> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
>
> be
>>
>> abandoned? Or (equivalently), what in the standard says that the behavior
>> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
>
> to
>>
>> completion?
>
> Yes, but 15.1 does *not* require abandoning 5.3.5.
>
>> If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
>> terminate when an exception is thrown while another exception has been
>> thrown but not caught) also deferred?
>
> Evidently, the implementation's delete [] must have a catch handler
> that
> completes 5.3.5, then rethrows the exception. 15.5.1 isn't deferred
> because
> the second exception happens within a catch handler.
>
>> Informally (and I realize that using that word in this kind of a
>
> discussion in
>>
>> this newsgroup puts me on very shaky ground), we say that when an
>
> exception
>>
>> is thrown, normal execution stops, and control is transferred to a handler
>
> (if
>>
>> there is one). When a throw expression executes, we stop executing
>
> looping
>>
>> constructs, jump out of blocks, etc. 3.5.3/6 is essentially dictating
>
> that
>>
>> compilers generate a looping construct.
>
> Agreed.
>
>> If we wrote the loop ourselves, we'd
>> stop executing it and propagate an exception (absent a catch block inside
>> the loop). Where does the standard say that the compiler-generated loop
>> doesn't do the same thing?
>
> Or a catch block containing the loop, that tries to complete the loop.
>
> template<class T>
> void mock_delete_array_op_throwing_destructor(T* p, size_t n)
> {
> try {
> while(0<n) p[--n]->~T();
> /*
> * If object file format doesn't provide the handwavium,
> * the implementation is automatically non-compliant.
> * I'd love to be wrong, but it's very unclear where this information
> * would reside in any of OMF, COFF, or ELF object formats
> * without vendor extensions.
> */
> catch(/* handwavium: exception type thrown by T::~T */& e)
> {
> while(0<n) p[--n]->~T();
> throw;
> }
> /*
> * more catch clauses, as above, until all exceptions throwable
> * by T::~T are caught
> */
> }
>
What's wrong with "catch(...)"?
try {
while(0<n) p[--n]->~T();
}
catch(...) {
try {
while(0<n) p[--n]->~T();
}
catch(...) {
abort();
}
throw;
}
cheers,
Martin
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Tue, 4 May 2010 22:29:45 CST Raw View
Martin B. wrote:
>
> What's wrong with "catch(...)"?
>
> try {
> while(0<n) p[--n]->~T();
> }
> catch(...) {
> try {
> while(0<n) p[--n]->~T();
> }
> catch(...) {
> abort();
> }
> throw;
> }
This is probably fine, except (1) per this thread, it's not clear
where the standard specifies this behavior and (2) instead of calling
abort, you need to call terminate (or, to be more precise, the
function you get back from a call to set_terminate).
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near
Seattle (http://cppandbeyond.com/)
* License my training materials for commercial
(http://tinyurl.com/yfzvkp9) or personal use
(http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Tue, 4 May 2010 22:30:21 CST Raw View
On May 4, 2:18 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> Kenneth 'Bessarion' Boyd wrote:
> > Evidently, the implementation's delete [] must have a catch handler
> > that
> > completes 5.3.5, then rethrows the exception.
>
> What is the part of the standard that makes this evident?
Any method for ensuring that 5.3.5 completes when a single destructor
call throws, is sufficient. The inferred original looping construct
is indeed abandoned.
I can only take this question to mean that you have in mind some way,
other than 15.3/[except.handle], to complete 5.3.5.
> > 15.5.1 isn't deferred
> > because
> > the second exception happens within a catch handler.
>
> Where does 15.5.1 require abandoning 5.3.5? 15.5.1 simply says that terminate
> is called. It does not say that terminate is "immediately" called.
Ahem..."In the following situations exception handling must be
abandoned for less subtle error handling techniques:" is how 15.5.1
starts off.
15.5.1 does not apply if some method of satisfying 5.3.5 in the
presence of 15.1, that does not require actually handling the first
thrown exception, is constructible.
Otherwise, failing to call terminate immediately is (tautologically?)
a failure to abandon exception handling for those statements executed
between the throwing of the second exception, and the call to
std::terminate.
I suppose one could argue that 15.5.1 generally causes mass
inconsistencies within the standard whenever it actually applies.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Tue, 4 May 2010 22:30:43 CST Raw View
On May 4, 2:29 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
> Kenneth 'Bessarion' Boyd wrote:
> > On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>
> >> Daniel Kr=FCgler wrote:
>
> > The requirements mentioned in [expr.delete] and [except.ctor] are
> >>> binding and an conforming implementation has to honor them. In
> >>> regard to the call of the deallocation function, this is also
> >>> required, see [expr.delete]/7:
>
> >> But presumably 15.1 is binding, too. What in the standard says that the
> >> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
>
> > be
>
> >> abandoned? Or (equivalently), what in the standard says that the behavior
> >> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
>
> > to
>
> >> completion?
>
> > Yes, but 15.1 does *not* require abandoning 5.3.5.
>
> > If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
> >> terminate when an exception is thrown while another exception has been
> >> thrown but not caught) also deferred?
>
> > Evidently, the implementation's delete [] must have a catch handler
> > that
> > completes 5.3.5, then rethrows the exception. 15.5.1 isn't deferred
> > because
> > the second exception happens within a catch handler.
>
> > Informally (and I realize that using that word in this kind of a
>
> > discussion in
>
> >> this newsgroup puts me on very shaky ground), we say that when an
>
> > exception
>
> >> is thrown, normal execution stops, and control is transferred to a handler
>
> > (if
>
> >> there is one). When a throw expression executes, we stop executing
>
> > looping
>
> >> constructs, jump out of blocks, etc. 3.5.3/6 is essentially dictating
>
> > that
>
> >> compilers generate a looping construct.
>
> > Agreed.
>
> > If we wrote the loop ourselves, we'd
> >> stop executing it and propagate an exception (absent a catch block inside
> >> the loop). Where does the standard say that the compiler-generated loop
> >> doesn't do the same thing?
>
> > Or a catch block containing the loop, that tries to complete the loop.
>
> > template<class T>
> > void mock_delete_array_op_throwing_destructor(T* p, size_t n)
> > {
> > try {
> > while(0<n) p[--n]->~T();
> > /*
> > * If object file format doesn't provide the handwavium,
> > * the implementation is automatically non-compliant.
> > * I'd love to be wrong, but it's very unclear where this information
> > * would reside in any of OMF, COFF, or ELF object formats
> > * without vendor extensions.
> > */
> > catch(/* handwavium: exception type thrown by T::~T */& e)
> > {
> > while(0<n) p[--n]->~T();
> > throw;
> > }
> > /*
> > * more catch clauses, as above, until all exceptions throwable
> > * by T::~T are caught
> > */
> > }
>
> What's wrong with "catch(...)"?
Nothing; I just had never seen in live code that it was possible to
rethrow an exception caught that way.
But there's a code example to that effect in 15.1. So my implied rant
about defective extant object file formats for C++ was out of line.
However, it does appear that the call to std::terminate is "injected
by the implementation".
> try {
> while(0<n) p[--n]->~T();}
>
> catch(...) {
> try {
> while(0<n) p[--n]->~T();
> }
> catch(...) {
> abort();
> }
> throw;
>
> }
I'm pretty sure this can be simplified to
try {
while(0<n) p[--n]->~T();}
catch(...) {
while(0<n) p[--n]->~T();
throw;
}
Also note that in my original improvised code fragment, calls to the
base delete [] (void*) operator are required both before normal return
and before rethrowing. The syntax of *that*, however, is not
transparent.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Wed, 5 May 2010 19:01:20 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
>
> On May 2, 11:16 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>>
>> Daniel Kr=FCgler wrote:
>
>>> The requirements mentioned in [expr.delete] and [except.ctor] are
>>> binding and an conforming implementation has to honor them. In
>>> regard to the call of the deallocation function, this is also
>>> required, see [expr.delete]/7:
>>
>> But presumably 15.1 is binding, too. What in the standard says that the
>> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
>
> be
>>
>> abandoned? Or (equivalently), what in the standard says that the behavior
>> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
>
> to
>>
>> completion?
>
> Yes, but 15.1 does *not* require abandoning 5.3.5.
Let's look at this another way. If I write a while loop, I think we
will agree that if an exception is thrown in the body of the loop, the
loop is abandoned. But the behavior of a while loop is dictated by
6.5.1/1:
> In the while statement the substatement is executed repeatedly until the value of the condition (6.4)
> becomes false. The test takes place before each execution of the substatement.
The word "exception" does not occur anywhere in 6.5, so there is
nothing that expressly says that iteration is abandoned if an
exception is thrown. The only way we can conclude, I think, that the
loop doesn't run to completion 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.
This suggests that 15.1/2 trumps other parts of the standard when it
comes to control flow: when an exception is thrown, you stop what you
are currently doing and you look for a handler. If you find one,
control is transferred to it; you don't go back to what you had been
doing at the time the exception was thrown.
So let's go back to 5.3.5/6, which reads:
> The delete-expression will invoke the destructor (if any) for the object or the elements of the array being
> deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in
> reverse order of the completion of their constructor; see 12.6.2).
There is no mention of exceptions anywhere in 5.3.5. So why should we
conclude that if an exception is thrown during the loop whose behavior
is dictated by 5.3.5/6, we defer looking for a handler until the loop
has finished executing, but if an exception is thrown during the loop
whose behavior is dictated by 6.5.1/1, we stop iteration immediately?
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near
Seattle (http://cppandbeyond.com/)
* License my training materials for commercial
(http://tinyurl.com/yfzvkp9) or personal use
(http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Thu, 6 May 2010 20:17:34 CST Raw View
On May 5, 8:01 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> Let's look at this another way. If I write a while loop, I think we
> will agree that if an exception is thrown in the body of the loop, the
> loop is abandoned. But the behavior of a while loop is dictated by
> 6.5.1/1:
>
> > In the while statement the substatement is executed repeatedly until the value of the condition (6.4)
> > becomes false. The test takes place before each execution of the substatement.
>
> The word "exception" does not occur anywhere in 6.5, so there is
> nothing that expressly says that iteration is abandoned if an
> exception is thrown. The only way we can conclude, I think, that the
> loop doesn't run to completion 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.
>
> This suggests that 15.1/2 trumps other parts of the standard when it
> comes to control flow: when an exception is thrown, you stop what you
> are currently doing and you look for a handler. If you find one,
> control is transferred to it; you don't go back to what you had been
> doing at the time the exception was thrown.
It would be nice if the standard had a clear notion of which parts
abrogate which others in case of an irreconcilable conflict, yes.
> So let's go back to 5.3.5/6, which reads:
>
> > The delete-expression will invoke the destructor (if any) for the object or the elements of the array being
> > deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in
> > reverse order of the completion of their constructor; see 12.6.2).
>
> There is no mention of exceptions anywhere in 5.3.5. So why should we
> conclude that if an exception is thrown during the loop whose behavior
> is dictated by 5.3.5/6, we defer looking for a handler until the loop
> has finished executing, but if an exception is thrown during the loop
> whose behavior is dictated by 6.5.1/1, we stop iteration immediately?
In the absence of a proper priority system regarding the sections of
the standard, I'm thinking in terms of "maximally compliant
implementation".
* The looping constructs don't have "internal structure" to speak of.
The implementation is faced with arbitrary loop content, with no
obvious simplifying assumptions that would allow efficiently
specifying a suitable "wrapping handler". It comes down to a simple
conflict between 6.5.1/1 and 15.1/2. Maximal compliance merely has to
choose which section is abrogated.
* the delete operator has much internal structure; the relevant
sections are not just 5.3.5 and 15.1/2, we also have 12.5/[class.free]
and 3.8/[basic.life] (this is not meant to be an exhaustive listing).
Furthermore, the looping construct is a means to an "observable
effect", the implementation is free to choose how to implement it to
maximize compliance in the face of destructors throwing exceptions.
Furthermore, within the looping construct it is "some statement or
expression" within the construct that throws the exception; the
construct itself doesn't throw exceptions. The delete operator is a
single statement.
That is, for:
unsigned int i = 10
do {
funcA();
funcB();
}
while(funcC(--i));
The do-while doesn't throw an exception. Assuming a typical
implementation where --i is a no-fail operation, the possible sources
of exceptions are one of funcA, funcB, or funcC. No handlers in
sight, simple conflict.
Whereas, considering this (where Alpha is a class with a potentially
throwing destructor)
Alpha* tmp = new unsigned Alpha[10];
delete [] tmp;
Assuming we get to the delete [] tmp; : it's a single expression.
There is *nothing* prohibiting the implementation of delete [] tmp;
from containing an exception handler; in fact, I don't immediately see
any clause requiring delete [] tmp to even let the destructor-thrown
exceptions escape. (The obvious implementation for an eat-all-
exceptions delete [] tmp; also avoids the risk of std::terminate.)
That said, I think as a matter of quality of implementation one would
expect one of the destructor-thrown exceptions to reliably escape,
subject to calling both all of the destructors, and the appropriate
delete [](void *). The only real motive for violating all of 5.3.5,
[class.free], and [basic.life] is to avoid risking invoking 15.5.1, so
I just don't see how a maximally compliant implementation can do that.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Thu, 6 May 2010 20:25:37 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
>
> On May 4, 2:29 pm, "Martin B." <0xCDCDC...@gmx.at> wrote:
>>
>> try {
>> while(0<n) p[--n]->~T();}
>>
>> catch(...) {
>> try {
>> while(0<n) p[--n]->~T();
>> }
>> catch(...) {
>> abort();
>> }
>> throw;
>>
>> }
>
> I'm pretty sure this can be simplified to
>
> try {
> while(0<n) p[--n]->~T();}
>
> catch(...) {
> while(0<n) p[--n]->~T();
> throw;
> }
These samples are not equivalent. If the second while throws, the first
code will abort, while the second code will simply propagate that
exception.
--
Dragan
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Fri, 7 May 2010 00:18:47 CST Raw View
On May 6, 9:25 pm, Dragan Milenkovic <dra...@plusplus.rs> wrote:
> These samples are not equivalent. If the second while throws, the first
> code will abort, while the second code will simply propagate that
> exception.
Checking...yes, see that (none of the std::terminate triggers apply)
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <NeverRead@aristeia.com>
Date: Fri, 7 May 2010 00:18:32 CST Raw View
Kenneth 'Bessarion' Boyd wrote:
>
> It would be nice if the standard had a clear notion of which parts
> abrogate which others in case of an irreconcilable conflict, yes.
It would be nicer if the standard didn't have conflicting parts :-)
> In the absence of a proper priority system regarding the sections of
> the standard, I'm thinking in terms of "maximally compliant
> implementation".
I'm going to interpret this as "I see nothing in the standard that
unambiguously says what should happen when an exception is emitted by
a destructor called as a result of a delete[] expression."
To be honest, I get the impression that you desperately want
destruction of the array elements to continue after an exception is
thrown, and I'd be interested to know why. Note that for STL
containers, behavior is undefined if an element's destructor throws
during container destruction (per 17.4.3.6/2). So if destroying a
vector<T> yields UB if a T destructor throws, why not just say that UB
results if a T destructor throws inside a delete[] expression for an
array of T?
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near
Seattle (http://cppandbeyond.com/)
* License my training materials for commercial
(http://tinyurl.com/yfzvkp9) or personal use
(http://tinyurl.com/yl5ka5p).
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: restor <akrzemi1@gmail.com>
Date: Fri, 7 May 2010 17:55:10 CST Raw View
> But presumably 15.1 is binding, too. What in the standard says that the
> behavior dictated by 15.1 doesn't cause the behavior dictated by 5.3.5 to
be
> abandoned? Or (equivalently), what in the standard says that the behavior
> dictated by 15.1 is deferred until the behavior dictated by 5.3.5 has run
to
> completion?
>
> If 15.1 is deferred until 5.3.5 is done, why isn't 15.5.1 (invocation of
> terminate when an exception is thrown while another exception has been
thrown
> but not caught) also deferred?
Hi,
I wonder whether the issue you are describing is acadeamic or
practical. If it is the former, then I guess you are right to observe
that the behavior is not sufficiently specified, and may also be right
that the intuitive interaction between loops and exceptions is counter
to the C++ standard. If your question is practical, I can offer the
following reasoning.
(1) The C++ standard does not require that automatic objects be placed
on the stack. In fact they may be allocated anywhere and anyhow. The
only thing that is required is that their destructors be called at the
end of the scopes in the proper order. The only certain difference
between the two codes:
{ Type t;
use(t);
}
{ Type* t = new T;
use(t);
delete t;
}
is that in the latter you have to call the destructor manually. But
regarding where the data for t is stored, in both cases it may be
stored on the heap. It is unobjectable that in the former case the
memory will be freed even if the destructor throws, and since the way
the data is stored in the former and the latter case _may_ be the
same, we must accept that in case of operator delete, memory will be
released on destructor throw too.
(2) In the following example:
{ Type t0;
Type t1;
use(t0, t1);
}
we accept that if t1's destructor throws, t0's destructor will still
be called. Morover, in such a case if t0's destructor throws too, we
terminate.
(3) If we accept that the following example
{ Type t[2];
use(t[0], t[1]);
}
is no different than this in (2), we also accept that if t[1]'s
destructor throws, t[0]'s destructor will still be called.
(4) Now, if we observe that (3) is different from:
{ Type* t = new Type[2];
use(t[0], t[1]);
delete [] t;
}
only in manually calling destructors, and may not differ in how the
objects are allocated, we must also accept that if t[1]'s destructor
throws, t[0]'s destructor will still be called, and the deallocation
function will be called in the end; (unless both destructors throw).
Regards,
&rzej
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Kenneth 'Bessarion' Boyd" <zaimoni@zaimoni.com>
Date: Sat, 8 May 2010 07:27:57 CST Raw View
On May 7, 1:18 am, Scott Meyers <NeverR...@aristeia.com> wrote:
> Kenneth 'Bessarion' Boyd wrote:
> > In the absence of a proper priority system regarding the sections of
> > the standard, I'm thinking in terms of "maximally compliant
> > implementation".
>
> I'm going to interpret this as "I see nothing in the standard that
> unambiguously says what should happen when an exception is emitted by
> a destructor called as a result of a delete[] expression."
Unfortunately, yes. The exception-eating delete [] is more of an
exercise in solipsistic perversity; real implementations are expected
to relay some exception out.
It's relatively easy to implement "first exception suppresses later
ones", but I think an implementation-specified choice would be
sufficient. In any case, I'd like the exception-eating delete []
explicitly ruled out of C++1X if there is indeed no derivation in
earlier drafts of C++.
I do think there's a non-zero plausibility of an actual defect (direct
self-contradiction) here, but both demonstrating and fixing that will
have to wait for C++1X. "Maximally compliant implementation" allows
me to both get a useful answer, and have a clear gestalt of what would
be a mistaken exposition.
> To be honest, I get the impression that you desperately want
> destruction of the array elements to continue after an exception is
> thrown, and I'd be interested to know why.
That would be a very long digression into why I'm focusing on
"maximally compliant implementation" in the first place. Of course,
if we have a genuine self-contradiction when a destructor call throws
in delete [], then "anything goes" formally and the following is just
a quality of implementation issue. But I don't think that's been
documented either.
Since a easy-to-describe pseudo-code for implementing operator delete
[] is a template function, the prior code examples I've tossed out
indicate that it shouldn't be too hard to construct a delete []
implementation that makes all of the destructor calls regardless of
whether they throw. (Using the laxity that stack unwinding is assumed
not to be happening).
So it's very plausible (if not constructively proven) that a maximally
compliant implementation *can* simultaneously comply with 15.1/2,
5.3.5, [basic.life], etc., when a single destructor throws within
delete [] . The real problem with 2 or more destructors throwing, is
that delete [] can only relay one exception out.
> Note that for STL
> containers, behavior is undefined if an element's destructor throws
> during container destruction (per 17.4.3.6/2). So if destroying a
> vector<T> yields UB if a T destructor throws, why not just say that UB
> results if a T destructor throws inside a delete[] expression for an
> array of T?
Interesting; it would be a very convenient implementation property if
reinterpret-casting an n-element C array to its corresponding
tr1::array, and back, "just worked" (for C++0X, use std::array rather
than tr1::array).
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]