Topic: exceptions
Author: hendrik@vedge.com (Hendrik Boom)
Date: Mon, 09 Aug 1993 13:27:21 GMT Raw View
parrott@ed8200.ped.pto.ford.com (Dennis Parrott) writes:
: Kevin J Hopps (hopps@yellow.mmm.com) wrote:
: : I am wondering about the progress toward the support of exceptions
: : both in the standard and in existing c++ compilers.
: :
: : Are there any c++ compilers that presently support exceptions as
: : proposed in the ARM?
:
: DEC C++ has support for exceptions. I am *NOT* expert enough to tell you
: if it conforms to the ARM 100% or not. It has catch, throw, try so I
: guess that it is "close".
:
: If you need more info than that, please let me know by e-mail and I'll
: look at the docs I have to see how close to the ARM it comes.
IBM supports exceptions in their C++ compilers, both on their workstations
and on OS/2. As far as I know, they conform to the ARM. In fact,
every time I thought I found a bug in their C++ implementation,
however obscure or absurd, a close reading of the ARM revealed
that they were right and I was wrong. This level of detailed
conformance presumably extends to exceptions.
:
: dennis parrott
: parrott@pt0503.ped.pto.ford.com
--
-------------------------------------------------------
Try one or more of the following addresses to reply.
at work: hendrik@vedge.com, iros1!vedge!hendrik
at home: uunet!ozrout!topoi!hendrik
Author: parrott@ed8200.ped.pto.ford.com (Dennis Parrott)
Date: Wed, 4 Aug 1993 14:42:14 GMT Raw View
Kevin J Hopps (hopps@yellow.mmm.com) wrote:
: I am wondering about the progress toward the support of exceptions
: both in the standard and in existing c++ compilers.
:
: Are there any c++ compilers that presently support exceptions as
: proposed in the ARM?
DEC C++ has support for exceptions. I am *NOT* expert enough to tell you
if it conforms to the ARM 100% or not. It has catch, throw, try so I
guess that it is "close".
If you need more info than that, please let me know by e-mail and I'll
look at the docs I have to see how close to the ARM it comes.
dennis parrott
parrott@pt0503.ped.pto.ford.com
Author: rsilton@coolpro.melpar.esys.com (Rick Silton)
Date: Thu, 5 Aug 1993 02:27:03 GMT Raw View
parrott@ed8200.ped.pto.ford.com (Dennis Parrott) writes:
>Kevin J Hopps (hopps@yellow.mmm.com) wrote:
>: I am wondering about the progress toward the support of exceptions
>: both in the standard and in existing c++ compilers.
>:
>: Are there any c++ compilers that presently support exceptions as
>: proposed in the ARM?
>DEC C++ has support for exceptions. I am *NOT* expert enough to tell you
>if it conforms to the ARM 100% or not. It has catch, throw, try so I
>guess that it is "close".
Watcom advertises that it has true exceptions for it's 32 bit C/C++ compiler.
- Rick Silton
Author: kanze@us-es.sel.de (James Kanze)
Date: 4 May 93 18:19:21 Raw View
In article <1993May1.181659.3884@nntpd.lkg.dec.com>
mjg@ktbush.ogo.dec.com (Michael J. Grier) writes:
|> In article <pathak-220493150703@virtual.mitre.org>, pathak@mitre.org (Heeren Pathak) writes:
|> |>The correct code block should be...
|> |>void sub() {
|> |> Foo* f = (Foo*)0;
|> |> try {
|> |> f = new Foo(/*arguments*/);
|> |> }
|> |> catch (...) {
|> |> delete f;
|> |> }
|> |>
|> If you insist on performing the object creation inside the try block,
|> you should really make sure that f is volatile. Otherwise, there is no
|> guarantee that it'll assume its modified value inside the catch().
I don't think that volatile will make any difference here. If an
exception is thrown, then the catch clause will be executed *before*
the assignment to 'f' takes place.
Of course, the final version of the standard will have to specify how
volatile and exceptions interact, but I don't think that this will be
a big problem.
|> A better way to write it is:
|> void sub() {
|> Foo *f = new Foo(/* arguments */);
|> try {
|> // do something which can raise an exception here
|> }
|> catch (...) {
|> delete f;
|> }
Since it was the constructor for Foo which could raise the exception,
this will result in an unexpected exception.
|> If the "new" fails, there's no reason to believe that "f" will contain
|> the partially constructed value anyways, so to try to handle failures of
|> new is pointless. (We had a situation very much like this, and ran into
|> a problem wherein if "f" were allocated to a register, it would revert to
|> its null value within the catch, generating memory leaks.) If a failure
|> within "new" results in lost storage, it's a quality issue with your operator
|> new, the Foo() constructor, and/or your compiler.
I'll go for the compiler, since the alternative is awfully painful. I
think that this is the feeling of the standards committee as well.
--
James Kanze email: kanze@us-es.sel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
-- Beratung in industrieller Datenverarbeitung
Author: reindorf@us-es.sel.de (Charles Reindorf)
Date: Wed, 28 Apr 93 16:42:43 GMT Raw View
------------------------------------------------------ POINT I:
I should like to add a further example to the ones so-far about
exceptions. Consider some class X and the following attempt to
allocate an array of such objects on the heap:
X *f = new X[5];
Now, what happens if, say, the construction of X[3] throws an
exception?
The ARM section 15.3c states:
... Also, should a constructor for an element of an automatic
array throw an exception, only the constructed elements
of that array will be destroyed ...
Note the use of the word "automatic". This means that there is
no call to the destructors for the elements X[0] .. X[2].
Moreover, it leaves a gap, because the client function really
has little / no idea of which elements to destruct (as if it
should?).
---------------------------------------------------- POINT II:
I think that the "best-approximation" interpretation of the ARM
is that there is NO attempt to call destructors for objects
created on the heap under any circumstances. This means that in:
X *f = new X;
if an exception gets thrown in the constructor for X, then no
attempt will ever be made to destruct any bases or members of X
as part of the stack-unwind. This is also a terrible gap, because
it is impossible for the client to determine how to destruct the
correct parts of X. Also, the constructor for X cannot have done
this by any stretch of the imagination, because the exception
might itself have been thrown in a base/member constructor, and
no "try" block for the body of X's constructor will have been
entered by that time
(some discussions on this theme hav occured with "smart pointers"
but they all assume some attempt will be made to destruct the
members anyway).
POINTS I&II surely indicate that some re-wording in the
forthcomming standard is required here.
---------------------------------------------------------- POINT III:
Let us assume that some work is done on the standard to as to
ensure that it is feasible for a constructor to execute safely:
I.e. even if an exception propagates through it, the object is
returned to some sort of "completely unconstructed" state.
This STILL does not solve the question of how can the memory
which was allocated for the forthcomming (but unsucessfully
constructed) new object be deleted?
Let us assume that in "f = new X", some space is allocated for
X via. "new", and then the constructor for X is called into
it. Then it throws an exception. Let us assume that we can
"do things" which make sure that the space occupied by X is
somehow "cleaned up" so as to remove any sub-allocated
resources.
Then how does the system return the memory to be occupied by
the failed X? Perhaps it codes a call to the "delete" function
(as part of the unwind process): This also requires some more
word in the specification.
But that is OK for the default "new" and "delete", but not
necessarily for placement versions. For example, the standard
placement version of "new" DEFINATELY should not require a
call to delete as part of the unwind process. On the other
hand, other (user defined) placemenet versions of new (perhaps
one which allocates memory from specific sub-heaps) will
definately require a call to some sort of "delete" function
during the unwind. So how can the compiler/system know which
whether to call delete? or even which version.
On approach, which "a little birdy" told me about, was to
consider the "return" statement from a new operator as the
subsequent call to the constructor for the relevant object.
This can then be wrapped in a try block (at "new") so that
the "new" function itself knows if the constructor threw an
exception and invoke the correct recovery of resources
---------------------- END OF RAMBLE
Regards
Charles Reindorf
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 30 Apr 1993 16:14:13 GMT Raw View
In article <6285@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
>John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
>: In article <9311802.24596@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>
>: >Moral: either
>: > (a) Don't use raw pointers as members. Use a smart pointer class instead.
>: Might have to. Mm. Have to put an 'owner' flag in it too,
>: so it knows whether to delete the object on destruction or not.
>
>No. "Non-owning" pointers can be raw pointers. "Owning" pointers
>must be smart pointers, since allocating dynamic memory is
>"resource acquisition", which (according to the ARM, CPL2 and other books)
>should be modelled as "object initialization".
This works iff its determined in advance. It may not be.
One may want char* that point to either heap or static strings,
for example. The flagged smart pointer is an advantage here,
because the 'static' attribute can be tracked (provided you tell
the constructor the right thing).
>Instead, assign new'ed objects to pointer-members in the constructor body.
>Try/catch can then be used for cleanup purposes.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 30 Apr 93 10:50:25 GMT Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
: In article <9311814.13184@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
: >>>Moral: either
: >>> (a) Don't use raw pointers as members. Use a smart pointer class instead.
: >>
: >> Might have to. Mm. Have to put an 'owner' flag in it too,
: >>so it knows whether to delete the object on destruction or not.
: >
: >If you don't own the object pointed to, then you don't need to use
: >a smart pointer class. In fact you don't have to worry about deallocation
: >at all.
[...]
: Miss the point: I may not know whether I own the object
: or not on a whole class basis. I might depend on which
: constructor is invoked.
That would IMHO be a strange setup. No need for it in general though;
it should be clear whether or not the pointer (smart or dumb) manages
the pointed-to object. You can't represent ownership with raw pointers,
so why do you think it must be supported by smart pointers? Non-owning
smart pointers (if you really need them) should be a separate class
(template).
: >> We could change that [the ARM] easily. The alternative
: >>is to use smart pointers everwhere for everything.
: >
: >I might point out that smart pointers have other benefits, too:
: >they mean you don't have to write copy-constructors or assignment
: >operators, because the default ones actually work right.
: Yes, but they have a disadvantage: they do not
: convert to smart pointers to base classes. In other words,
: they do not support polymorphism.
: Many times one uses pointers instead of actual
: objects exactly to support polymorphism.
[...]
: f(Ptr<Base>) { .. }
: Ptr<Derived> d;
: f(d); // error
IMHO, f() should take a Base*. If Ptr<T> has an operator T*, then
you can have automatic conversion to base class pointers. This requires
that the standards committee makes a favourable decision about temporaries
though, since a temporary Ptr<T> passed to f() must be guaranteed to stay
alive until f() is finished (which is not guaranteed by the ARM).
--
-------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-977 75 Lulea, Sweden |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490 |
-------------------------------------------------------------------------
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 1 May 1993 16:51:19 GMT Raw View
In article <6302@holden.lulea.trab.se> jbn@lulea.trab.se (Johan Bengtsson) writes:
>John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
>: In article <9311814.13184@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>
>: >>>Moral: either
>: >>> (a) Don't use raw pointers as members. Use a smart pointer class instead.
>: >>
>: >> Might have to. Mm. Have to put an 'owner' flag in it too,
>: >>so it knows whether to delete the object on destruction or not.
>: >
>: >If you don't own the object pointed to, then you don't need to use
>: >a smart pointer class. In fact you don't have to worry about deallocation
>: >at all.
>[...]
>
>: Miss the point: I may not know whether I own the object
>: or not on a whole class basis. I might depend on which
>: constructor is invoked.
>
>That would IMHO be a strange setup.
I agree. But it is not impossible to imagine, even if
its bad practice.
>No need for it in general though;
>it should be clear whether or not the pointer (smart or dumb) manages
>the pointed-to object. You can't represent ownership with raw pointers,
>so why do you think it must be supported by smart pointers?
If smart pointers dont destroy their objects automatically,
the do not solve the original problem, and if they do, they might
delete a non-owned object.
It is easy to imagine a string class:
class string {
char * data;
public:
enum owner {no,yes} mine;
string(char *x, owner o) : data(x), mine(o)
{
if(mine==yes)data=strdup(data);
}
~string() {if(owner==yes)free(data); }
};
I'm not saying this is a good idea. But on some systems,
there are limits of 64K on data, but no limits at all
on code space, and you might want to use such a nasty
trick.
>: f(Ptr<Base>) { .. }
>
>: Ptr<Derived> d;
>: f(d); // error
>
>IMHO, f() should take a Base*. If Ptr<T> has an operator T*, then
>you can have automatic conversion to base class pointers.
You can borrow my pointer as long as you dont
delete it, or give it to some untrustworthy scoundrel who will?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: mjg@ktbush.ogo.dec.com (Michael J. Grier)
Date: Sat, 1 May 1993 18:16:59 GMT Raw View
In article <pathak-220493150703@virtual.mitre.org>, pathak@mitre.org (Heeren Pathak) writes:
|>The correct code block should be...
|>void sub() {
|> Foo* f = (Foo*)0;
|> try {
|> f = new Foo(/*arguments*/);
|> }
|> catch (...) {
|> delete f;
|> }
|>
If you insist on performing the object creation inside the try block,
you should really make sure that f is volatile. Otherwise, there is no
guarantee that it'll assume its modified value inside the catch().
A better way to write it is:
void sub() {
Foo *f = new Foo(/* arguments */);
try {
// do something which can raise an exception here
}
catch (...) {
delete f;
}
If the "new" fails, there's no reason to believe that "f" will contain
the partially constructed value anyways, so to try to handle failures of
new is pointless. (We had a situation very much like this, and ran into
a problem wherein if "f" were allocated to a register, it would revert to
its null value within the catch, generating memory leaks.) If a failure
within "new" results in lost storage, it's a quality issue with your operator
new, the Foo() constructor, and/or your compiler.
------------------------------------------------------------------------
I'm saying this, not Digital. Don't hold them responsibile for it!
Michael J. Grier Digital Equipment Corporation
(508) 496-8417 mjg@ktbush.ogo.dec.com
Stow, Mass, USA Mailstop OGO1-1/E16
Author: pathak@mitre.org (Heeren Pathak)
Date: Sat, 24 Apr 1993 17:50:09 GMT Raw View
In article <1993Apr23.182924.17465@microsoft.com>, jimad@microsoft.com (Jim
Adcock) wrote:
>
> In article <pathak-220493150703@virtual.mitre.org> pathak@mitre.org (Heeren Pathak) writes:
> |I believe this is where you are confusing memory allocation with object
> |construction and thus making a bad assumption. Operator new did complete
> |successfully so the memory was allocated and 'f' points to this allocated
> |memory block. The constructor failed so the memory block may not be
> |initialized correctly. The memory was allocated and performing a delete f
> |releases the allocated memory.
>
> You're assuming that 'f' is pointed at the allocation before the construction
> is attempted. I don't think ARM specifies this behavior, but rather leaves
> it unspecified [as far as I can find]. The ARM does talk about how
> implementation can either call operator new from inside the constructor,
> or can have a separate call to operator new before the constructor is called.
> The obvious ways of implementing these two approaches would seem to give
> two different values for 'f' when an exception happens in the constructor.
>
> I suggest that this problem has been left unspecified to date, and
> needs to be specified. Perhaps someone can call out the appropriate
> sections in ARM if I've missed something.
Jim,
I was making the wrong assumption. Here is a partial copy of a followup
posting that explains the problem along with the relevant quotations from
ARM. Are you on the committee by any chance? If so, can you forward this
problem to the committee.
-------
It seems to me that ARM has a loophole that might cause problems when
constructors throw an exception.
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sun, 25 Apr 1993 04:15:06 GMT Raw View
pathak@mitre.org (Heeren Pathak) writes:
>The correct code block should be...
>void sub() {
> Foo* f = (Foo*)0;
> try {
> f = new Foo(/*arguments*/);
> }
> catch (...) {
> delete f;
> }
>
>Note: It is okay to 'delete f'. See below.
This code won't prevent a memory leek. See below.
>hopps@yellow.mmm.com (Kevin J Hopps) wrote:
>>
>> Would somebody please answer the following "Question" items?
>>
>> Assumption: I call "new X" and an exception is thrown.
>> Fact: If the failure occured during allocation, there is nothing
>> that must be "cleaned up". Therefore make the
>
>If new threw the exception, the no problem since 'f' was previous
>initialized to null. Since delete null is valid and safe operation,
>everything is okay.
>
>> Assumption: The failure occured after successful allocation, during
>> construction.
>> Fact: If a memory leak is to be avoided, the allocated memory must
>> be released.
>> Fact: The caller of "new X" never learns where the memory is,
>> because operator new never returns.
>
>I believe this is where you are confusing memory allocation with object
>construction and thus making a bad assumption. Operator new did complete
>successfully so the memory was allocated and 'f' points to this allocated
>memory block. The constructor failed so the memory block may not be
>initialized correctly. The memory was allocated and performing a delete f
>releases the allocated memory.
I believe you are also confusing things here. Operator new() did complete
successfully so the memory was allocated, but 'f' does NOT point to this
allocated memory block. Thus 'delete f;' doesn't release the memory.
The statement
f = new X;
has the following effects:
(1) the code begins to evaluate the expresion (new X)
(1.1) the constructor for X is called
(1.2) operator new() is called to allocate memory
(1.3) operator new() returns
(1.4) the constructor initializes the memory
(1.5) the constructor returns
(2) the evaluation of (new X) is now complete
(3) f is assigned the address of the newly allocated memory
Note that this assumes that operator new() is called by the constructor.
This is the scheme used by Cfront, but other compilers might call
operator new() from the code to evaluate (new X) instead. This would
result in the following alternate order:
(Alt 1.1) operator new() is called to allocate memory
(Alt 1.2) operator new() returns
(Alt 1.3) the constructor for X is called
(1.4) the constructor initializes the memory
(1.5) the constructor returns
It's quite clear that (3) can't occur before (2), which is why your
suggested code doesn't work.
>> Fact: If a memory leak is to be avoided, operator new must catch
>> exceptions and release the memory.
I think you are confusing "new X" and "operator new".
The code that calls operator new() (ie. either the constructor
or the inline code that evaluates "new X") must catch exceptions,
not operator new(). It's only if an exception occurs after operator new()
has returned that there is a problem.
>> Question: When exceptions are implemented, will the default
>> operator new catch them, release acquired memory and
>> rethrow them?
Again, I think you should say "default code for a new expression" instead of
"default operator new".
I hope the answer to your question is "yes", but it does not appear to
be guaranteed by the ARM.
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 25 Apr 1993 18:26:07 GMT Raw View
In article <pathak-220493150703@virtual.mitre.org> pathak@mitre.org (Heeren Pathak) writes:
>In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
>wrote:
>>
>> cok@acadia.Kodak.COM (David Cok) writes:
>>
>> >In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>> >>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>> >>void
>> >>sub() {
>> >> ...
>> >> try {
>> >> Foo * f = new Foo(/* arguments */) ;
>> >> }
>> >> catch (...) {
>> >> // What can I do here? I don't know if the Foo was allocated
>> >> // or not. If it was, then it was the constructor that failed,
>> >> // but I don't know whether the storage was deallocated or not.
>> >> }
>> >>}
>> >>
>
>The above example already has a problem. The varible 'f' is enclosed
>within the try block. That means 'f' loses scope outside the try block and
>you now have a memory leak.
>
>The correct code block should be...
>void sub() {
> Foo* f = (Foo*)0;
> try {
> f = new Foo(/*arguments*/);
> }
> catch (...) {
> delete f;
> }
There is a better way.
>
>Note: It is okay to 'delete f'. See below.
>
>> Would somebody please answer the following "Question" items?
>>
>> Assumption: I call "new X" and an exception is thrown.
>> Fact: If the failure occured during allocation, there is nothing
>> that must be "cleaned up". Therefore make the
>
>If new threw the exception, the no problem since 'f' was previous
>initialized to null. Since delete null is valid and safe operation,
>everything is okay.
>
>> Assumption: The failure occured after successful allocation, during
>> construction.
>> Fact: If a memory leak is to be avoided, the allocated memory must
>> be released.
>> Fact: The caller of "new X" never learns where the memory is,
>> because operator new never returns.
>
>I believe this is where you are confusing memory allocation with object
>construction and thus making a bad assumption. Operator new did complete
>successfully so the memory was allocated and 'f' points to this allocated
>memory block.
No, I believe you are confusing operator new and the
keyword new. The expression
f = new X;
sets f to some value only after construction of the object is complete.
>The constructor failed so the memory block may not be
>initialized correctly. The memory was allocated and performing a delete f
>releases the allocated memory.
It also invokes the destructor, which assumes
the object was completely constructed .. which it wasnt.
It follows that a constructor must NOT throw an exception
until the object is in a state for which the destructor may
properly destroy the object.
So the constructor code itself must set a flag to say
"Im badly constructed" before throwing the exception. The
the destructor can take care about destroying the object.
>
>> Fact: If a memory leak is to be avoided, operator new must catch
>> exceptions and release the memory.
I'll bet there are alternatives to this, but I cant
think of one :-)
>> Question: When exceptions are implemented, will the default
>> operator new catch them, release acquired memory and
>> rethrow them?
How can it? It does not actually invoke the constructor.
As I picture it:
pointer = operator new; // get memory
X(pointer); // constructor called with hiddedn pointer
f = pointer;
What *that* means is that the *compiler* will be required
to trap exceptions from the constructor.
pointer = operator new;
try {
X(pointer);
}
catch (...) { delete pointer; //??
throw;
};
f = pointer;
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 25 Apr 1993 18:58:47 GMT Raw View
In article <1993Apr23.183208.8772@cdf.toronto.edu> g2devi@cdf.toronto.edu (Deviasse Robert N.) writes:
[exceptions during construction]
>
>Suppose we define the following class:
> class ConstructorFailure{
> private:
> void *ptr;
> public:
> ConstructorFailure() : ptr(0){}
> ConstructorFailure(void *mem) : ptr(mem) {}
> operator void*() const { return ptr; }
> };
>
>
>Then if the contructor fails, it can throw the exception: ConstructorFailure(this)
Nope. What if the constructor is a base class? Then 'this'
does not point to the allocated storage.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 25 Apr 1993 19:41:42 GMT Raw View
In article <9311514.26919@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>>> Question: When exceptions are implemented, will the default
>>> operator new catch them, release acquired memory and
>>> rethrow them?
>
>Again, I think you should say "default code for a new expression" instead of
>"default operator new".
>
>I hope the answer to your question is "yes", but it does not appear to
>be guaranteed by the ARM.
>
This is not the end of the story. The story is much harder.
Lets suppose that if I say:
f = new X;
then either
a) f is initialised to point to an X
b) the allocated memory is deallocated and an exception
is rethrown if there is a constructor failure
Now what happens here:
class Y {
X* x1, *x2;
Y() {
x1 = new X;
x2 = new X;
};
};
Y *y = new Y;
Suppose the allocation for x2 fails. What happens? Perhaps
y is deleted, but I dont see how 'x1' can be deleted.
So programmer intervention is required:
try { x1 = new X; }
catch ( .....
try { x2 = new X; }
catch ( ...
and the final rethrow gets trapped by keyword new to delete y.
OK? No its not:
X() : x1(new X), x2(new X) {}
How do I wrap 'try blocks' around mem initialisers?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Mon, 26 Apr 1993 05:51:51 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>
>>>> Question: When exceptions are implemented, will the default
>>>> operator new catch them, release acquired memory and
>>>> rethrow them?
>>
>>I hope the answer to your question is "yes", but it does not appear to
>>be guaranteed by the ARM.
>
> This is not the end of the story. The story is much harder.
>
> Lets suppose that if I say:
>
> f = new X;
>
>then either
>
> a) f is initialised to point to an X
> b) the allocated memory is deallocated and an exception
> is rethrown if there is a constructor failure
OK, fine.
>Now what happens here:
>
> class Y {
> X* x1, *x2;
> Y() {
> x1 = new X;
> x2 = new X;
> };
> };
>
> Y *y = new Y;
>
>Suppose the allocation for x2 fails. What happens? Perhaps
>y is deleted, but I dont see how 'x1' can be deleted.
>
>So programmer intervention is required:
>
> try { x1 = new X; }
> catch ( .....
> try { x2 = new X; }
> catch ( ...
>
>and the final rethrow gets trapped by keyword new to delete y.
>
>OK?
Yep, fine so far.
I'd code it differently myself, though - I'd use a smart pointer class.
Ptr<X> x1(new X);
Ptr<X> x2(new X);
The deallocation is handled automatically by the destructor, and the ARM
guarantees that automatic objects will have their destructors executed
when an exception occurs.
>No it's not:
>
> X() : x1(new X), x2(new X) {}
>
>How do I wrap 'try blocks' around mem initialisers?
You can use the same smart pointer class.
The ARM also guarantees that constructed subobjects will have their destructors
executed.
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Mon, 26 Apr 93 15:38:56 GMT Raw View
g2devi@cdf.toronto.edu (Deviasse Robert N.) writes:
>In article <hopps.735581081@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>>pathak@mitre.org (Heeren Pathak) writes:
>>
>>>In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
>>>wrote:
>>>>
>>>> cok@acadia.Kodak.COM (David Cok) writes:
>>>>
>>>> >In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>>>> >>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>>>> >>void
>>>> >>sub() {
>>>> >> ...
>>>> >> try {
>>>> >> Foo * f = new Foo(/* arguments */) ;
>>>> >> }
>>>> >> catch (...) {
>>>> >> // What can I do here? I don't know if the Foo was allocated
>>>> >> // or not. If it was, then it was the constructor that failed,
>>>> >> // but I don't know whether the storage was deallocated or not.
>>>> >> }
>>>> >>}
>>>> >>
>>
>>>The above example already has a problem. The varible 'f' is enclosed
>>>within the try block. That means 'f' loses scope outside the try block and
>>>you now have a memory leak.
>>
>>>The correct code block should be...
>>>void sub() {
>>> Foo* f = (Foo*)0;
>>> try {
>>> f = new Foo(/*arguments*/);
>>> }
>>> catch (...) {
>>> delete f;
>>> }
>>
>>>Note: It is okay to 'delete f'. See below.
>>
>>>> Even if I go through all the trouble of figuring out every different
>>>> kind of exception which can be thrown by "new X" today, the implementor
>>>> of X may change things tomorrow and break my code. Further, while catching
>>>> every different exception which can occur as a result of "new X" does
>>>> indeed let me distinguish between failures to allocate and failures to
>>>> construct, knowing that doesn't help if the failure was in the
>>>> construction and not the allocation. The reason is (and this has
>>>> already been stated in a previous posting) that there is no way to
>>>> *properly* release the memory acquired by new.
>>>>
>>>> Would somebody please answer the following "Question" items?
>>>>
>>>> Assumption: I call "new X" and an exception is thrown.
>>>> Fact: If the failure occured during allocation, there is nothing
>>>> that must be "cleaned up". Therefore make the
>>
>>>If new threw the exception, the no problem since 'f' was previous
>>>initialized to null. Since delete null is valid and safe operation,
>>>everything is okay.
>>
>>>> Assumption: The failure occured after successful allocation, during
>>>> construction.
>>>> Fact: If a memory leak is to be avoided, the allocated memory must
>>>> be released.
>>>> Fact: The caller of "new X" never learns where the memory is,
>>>> because operator new never returns.
>>
>>>I believe this is where you are confusing memory allocation with object
>>>construction and thus making a bad assumption. Operator new did complete
>>>successfully so the memory was allocated and 'f' points to this allocated
>>>memory block. The constructor failed so the memory block may not be
>>>initialized correctly. The memory was allocated and performing a delete f
>>>releases the allocated memory.
>>
>>Wrong! If new allocates the memory but the constructor throws the
>>exception, then the memory is allocated but f does not point to it,
>>since new doesn't return the pointer. You are correct in your above
>>statement which says if f is initialized to zero it is safe to delete
>>it. However, it doesn't point to the memory which was allocated.
>>
>>My original question still remains:
>Suppose we define the following class:
> class ConstructorFailure{
> private:
> void *ptr;
> public:
> ConstructorFailure() : ptr(0){}
> ConstructorFailure(void *mem) : ptr(mem) {}
> operator void*() const { return ptr; }
> };
>Then if the contructor fails, it can throw the exception: ConstructorFailure(this)
>So the above code can be written as:
>void sub() {
> Foo* f = NULL;
> try {
> f = new Foo(/*arguments*/);
> } catch (ConstructorFailure ptr) {
> delete ptr;
> } catch (...) {
> delete f;
> }
>}
As I said before, if the allocation succeeds but the constructor fails,
who deletes the memory for the object itself? Can't be the exception
handler because it cannot know the address of the object. Can't be the
object because it won't know the memory is dynamic vs automatic or static.
Has to be operator new.
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Mon, 26 Apr 93 15:42:04 GMT Raw View
pathak@mitre.org (Heeren Pathak) writes:
[ previous discussion deleted ]
>However, Section 12 of ARM has seems indicate otherwise. It seems to me
>that ARM has a loophole that might cause problems when constructors throw
>an exception.
>From Section 12.1:
>"For explicit uses of operator new(), it may also be the job of the
>constructor to call an operator new() function to allocate memory."
>Futhermore, Section 12.5 (p. 282) gives an example where operator new() is
>overloaded and states:
>"Here, an implementation is allows to place a call of operator new() in the
>body of the constructor to implement the constructor semantics (see 12.1)."
>Since the actually memory allocation *could* occur inside the constructor,
>there is no guarentee that 'f' is points to the allocated memory in the
>event where the constructor throws an exception. Also, I could not find
>any statement in ARM that requires this memory to be freed.
>Stating this, I believe there is a safe way to handle this problem.
>Consider:
>Foo* f = (Foo*)0;
>void* t = (void*)0;
>try {
> t = Foo::new(sizeof(Foo)); // Legal, operator new is static (ARM 12.5);
> f = new (t) Foo(...); // Placement new.
>}
>catch(...){
> if ( (f == (Foo*)0) && (t != (void*)0) )
> delete t;
>}
>In the above example, if allocation fails there isn't any problem. If the
>constructor fails, 'f' isn't assigned, but 't' points to the allocated
>memory and can be safely deleted.
This seems to work, but I'd sure hate to have to write this code
everytime I allocated a new object.
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Mon, 26 Apr 1993 09:54:36 GMT Raw View
In article <9311615.29120@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>>
>> a) f is initialised to point to an X
>> b) the allocated memory is deallocated and an exception
>> is rethrown if there is a constructor failure
>
>>Now what happens here:
>>
>> class Y {
>> X* x1, *x2;
>> Y() {
>> x1 = new X;
>> x2 = new X;
>> };
>> };
>>
>> Y *y = new Y;
>>
>>Suppose the allocation for x2 fails. What happens? Perhaps
>>y is deleted, but I dont see how 'x1' can be deleted.
>>
>>So programmer intervention is required:
>>
>> try { x1 = new X; }
>> catch ( .....
>> try { x2 = new X; }
>> catch ( ...
>>
>>and the final rethrow gets trapped by keyword new to delete y.
>>
>>OK?
>
>Yep, fine so far.
>I'd code it differently myself, though - I'd use a smart pointer class.
>
> Ptr<X> x1(new X);
> Ptr<X> x2(new X);
Hang on: you cant do this, at least unless my in-class
initialiser proposal is accepted :-)
Furthermore, Smart Pointers defeat polymorphism,
so you might not want to use them. There is another
technique, but in any case, let me translate:
class Y {
Ptr<X> x1, x2;
Y() {
x1 = new X;
x2 = new X;
}
};
>
>The deallocation is handled automatically by the destructor, and the ARM
>guarantees that automatic objects will have their destructors executed
>when an exception occurs.
Yes, but these are not automatic objects: they are embedded
in an object under construction.
>
>>No it's not:
>>
>> X() : x1(new X), x2(new X) {}
>>
>>How do I wrap 'try blocks' around mem initialisers?
>
>You can use the same smart pointer class.
>The ARM also guarantees that constructed subobjects will have their destructors
>executed.
Only if the destructor is invoked on the whole object.
Besides, you are assuming that 'x2' above will be
initialised to 0 first, then set to 'new X', that wil not
be the case, because x2 has not been initialised at all yet:
it is still raw memory.
If the object is only partially constructed, the best or worst
one could hope for is that the object is partially destructed.
The problem I see is that there is no mechanism to synchronise
destructor body code with constructor code that initialises
the 'main' subobject.
That is, I can see that it is possible that the
destructors for *base* subobjects be automatically invoked
if such base subobjects were completely constructed and not
otherwise, but I see no way to do this automatically
when a failure occurs duing the construction of members
of a class.
You cant invoke the destructor body for the
class because it assumes the class has been completely
constructed, but it hasnt been, and you cant omit the
destruction either, or those member that were completely
constructed will not be destroyed correctly: it is
not good enough to invoke the default destructor of each
member if the member is a pointer.
That leaves it up to the programmer to destroy
at least the partically constructed main subobject.
One can imagine that the construction has a number
of 'synch' points such that a failure requires
a specific set of actions depending on which
'synch' point was last passed without failure.
The way you suggest is that a partially constructed
object must *always* be destroyable by the complete
destructor body. That is, the destructor of a partially
complete subobject should be invoked by the compiler
when there is a fault, and the programmer *must*
ensure this will always take the correct action.
The required cooperation between the destructor
and body of a constructor is easy to achive by using
a variable to hold the current state of construction
the synch point. The body could just increment an integer
at each synch point.
Mm:
class Y {
int steps_done;
X x1, x2, x3;
X() : steps_done(0),
x1(++steps_done, new X),
x2(++steps_done, new X),
x3(++steps_done, new X)
{
++steps_done;
x1=new X(x1);
++steps_done;
};
~X(){
switch(steps_done) {
case 7: ...
case 6: ...
}
}
};
Mm.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: pathak@mitre.org (Heeren Pathak)
Date: Mon, 26 Apr 1993 17:09:31 GMT Raw View
In article <hopps.735838462@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
wrote:
>
> pathak@mitre.org (Heeren Pathak) writes:
> >Foo* f = (Foo*)0;
> >void* t = (void*)0;
>
> >try {
> > t = Foo::new(sizeof(Foo)); // Legal, operator new is static (ARM 12.5);
> > f = new (t) Foo(...); // Placement new.
> >}
> >catch(...){
> > if ( (f == (Foo*)0) && (t != (void*)0) )
> > delete t;
> >}
>
> >In the above example, if allocation fails there isn't any problem. If the
> >constructor fails, 'f' isn't assigned, but 't' points to the allocated
> >memory and can be safely deleted.
>
> This seems to work, but I'd sure hate to have to write this code
> everytime I allocated a new object.
Agreed. Hopefully the committe will fix the problem in the language spec.
However until they do, this (or someother similar mechanism) seems to be
the only guaranteed way to prevent memory leaks during construction.
-------------------------------------------------------------------------
Heeren Pathak | Millions long for immortality who do
pathak@mitre.org | not know what to do with themselves
Mitre Corporation | on a rainy Sunday afternoon.
(617) 271-7465 | -- Susan Ertz
-------------------------------------------------------------------------
Disclaimer: Mine not Mitre's.
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Tue, 27 Apr 1993 08:34:38 GMT Raw View
jimad@microsoft.com (Jim Adcock) writes:
[regarding "f = new X;"]
>You're assuming that 'f' is pointed at the allocation before the construction
>is attempted. I don't think ARM specifies this behavior, but rather leaves
>it unspecified [as far as I can find]. The ARM does talk about how
>implementation can either call operator new from inside the constructor,
>or can have a separate call to operator new before the constructor is called.
>The obvious ways of implementing these two approaches would seem to give
>two different values for 'f' when an exception happens in the constructor.
>
>I suggest that this problem has been left unspecified to date, and
>needs to be specified. Perhaps someone can call out the appropriate
>sections in ARM if I've missed something.
The ARM has no notion of sequence points like the C standard.
It does however note that "The order of evaluation of subexpressions is
determined by the expression grammar". At least by my interpretation,
this would imply that the allocation-expression "new X" is evaluated
completely before evaluation of the assignment-expression "f = new X".
Thus I would say that the ARM guarantees that f is not assigned to before
the constructor call. Even if there is a separate call to operator new
before the constructor is called, the assignment to f must come
after the constructor call.
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Tue, 27 Apr 1993 16:29:17 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>>
>>I'd code it differently myself, though - I'd use a smart pointer class.
>>
>> Ptr<X> x1(new X);
>> Ptr<X> x2(new X);
>
> Hang on: you cant do this, at least unless my in-class
>initialiser proposal is accepted :-)
Sorry, I forgot x1 & x2 were class members here.
> Furthermore, Smart Pointers defeat polymorphism,
>so you might not want to use them. There is another
>technique,
Hmm. Could you elaborate?
>but in any case, let me translate:
>
> class Y {
> Ptr<X> x1, x2;
> Y() {
> x1 = new X;
> x2 = new X;
> }
> };
Well, when we are talking about class members x1, x2, I would use
a member initializer to initialize them
Y() : x1(new X), x2(new X) {}
rather than assignment statements in the constructor.
Otherwise the default constructor initializes them to 0 first, which
is inefficient and unnecessary.
>>>How do I wrap 'try blocks' around mem initialisers?
>>
>>You can use the same smart pointer class.
>>The ARM also guarantees that constructed subobjects will have their
>>destructors executed.
>
> Only if the destructor is invoked on the whole object.
No. The ARM says:
As control passes from a throw-point to a handler, destructors are
invoked for all automatic objects constructed since the
try-block was entered.
**An object that is partially constructed will have destructors
executed only for fully constructed sub-objects**.
Also, should a constructor for an element of an automatic array
throw an exception, only the constructed elements of that array
will be destroyed.
I take that to imply that if an object is partially constructed, then
the destructors for it's fully constructed sub-objects (including members),
*will* be executed. Clearly it implies that the destructor for
the whole object will *not* be executed.
> Besides, you are assuming that 'x2' above will be
>initialised to 0 first, then set to 'new X', that wil not
>be the case, because x2 has not been initialised at all yet:
>it is still raw memory.
No, I'm not assuming that. I'm assuming that if an exception occurs
during the construction of x2, then the destructor for x1 will get
executed.
> If the object is only partially constructed, the best or worst
>one could hope for is that the object is partially destructed.
Yes. That is sufficient though, so long as the partial destruction
exactly mirrors the partial construction, as appears is guaranteed
by the ARM.
> The problem I see is that there is no mechanism to synchronise
>destructor body code with constructor code that initialises
>the 'main' subobject.
>
> That is, I can see that it is possible that the
>destructors for *base* subobjects be automatically invoked
>if such base subobjects were completely constructed and not
>otherwise, but I see no way to do this automatically
>when a failure occurs duing the construction of members
>of a class.
>
> You cant invoke the destructor body for the
>class because it assumes the class has been completely
>constructed, but it hasnt been,
As I said above, the ARM guarantees that this won't happen.
>and you cant omit the
>destruction either, or those member that were completely
>constructed will not be destroyed correctly: it is
>not good enough to invoke the default destructor of each
>member if the member is a pointer.
Yes, you are right, it's not good enough to just invoke the
destructor for pointers, since the destructor for pointers is
a no-op, not a delete.
Nevertheless, that is all that will happen, according to the ARM.
Moral: either
(a) Don't use raw pointers as members. Use a smart pointer class instead.
(At present, the ARM doesn't appear to guarantee that
allocation-expressions such as "new X" don't leak memory if an
exception occurs in the constructor, but that is hopefully an
accidental omission which will soon be corrected. Assuming
that allocation-expressions are safe, then you can write a
smart pointer class that is safe in the presence of exceptions.)
or
(b) Have the constructor catch exceptions if it gets interrupted
part-way through, and clean up after itself.
> That leaves it up to the programmer to destroy
>at least the partically constructed main subobject.
Not for members with destructors, including smart pointers - this is the
compilers job. Only for raw pointers is it the programmers job,
in which case the programmers *mustn't* use mem-initialisers for any
but the first raw pointer member, in order to be able to catch exceptions
and clean up properly.
Eg.
class Y {
X *x1, *x2, *x3;
public:
Y() : {
x1 = new X; // could use mem-initializer instead here,
// but only for x1, not for x2 or x3
// might as well use assignment for symmetry
try {
x2 = new X;
try {
x3 = new X;
}
catch(...) { delete x2; throw; }
}
catch(...) { delete x1; throw; }
}
~Y() { delete x3; delete x2; delete x1; } // order irrelevant
}
With a smart pointer class it's a lot simpler:
class Y {
Ptr<X> x1, x2, x3;
Y() : x1(new X), x2(new X), x3(new X);
~Y() { delete x3; delete x2; delete x1; }
};
>One can imagine that the construction has a number
>of 'synch' points such that a failure requires
>a specific set of actions depending on which
>'synch' point was last passed without failure.
>
> The way you suggest is that a partially constructed
>object must *always* be destroyable by the complete
>destructor body. That is, the destructor of a partially
>complete subobject should be invoked by the compiler
>when there is a fault, and the programmer *must*
>ensure this will always take the correct action.
No. The compiler should not invoke the main object's destructor,
because the destructor must only be called on a completely constructed
object. Instead the constructor must catch exceptions at each of the synch
points, and do the cleanup itself. (It seems easier to avoid synch-points
in the first place by using a smart pointer class.)
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Tue, 27 Apr 1993 21:43:10 GMT Raw View
In article <9311802.24596@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>No. The ARM says:
>
> As control passes from a throw-point to a handler, destructors are
> invoked for all automatic objects constructed since the
> try-block was entered.
>
> **An object that is partially constructed will have destructors
> executed only for fully constructed sub-objects**.
> Also, should a constructor for an element of an automatic array
> throw an exception, only the constructed elements of that array
> will be destroyed.
>
>I take that to imply that if an object is partially constructed, then
>the destructors for it's fully constructed sub-objects (including members),
>*will* be executed. Clearly it implies that the destructor for
>the whole object will *not* be executed.
Yes, and so the ARM is wrong, because it HAS to be.
The destructor for a pointer does nothing, yet the attached
storage must be deleted. If the pointer is initialised
in a mem-initialiser there is no *other* was that I can
see for the object to be properly destroyed.
>
>> If the object is only partially constructed, the best or worst
>>one could hope for is that the object is partially destructed.
>
>Yes. That is sufficient though, so long as the partial destruction
>exactly mirrors the partial construction, as appears is guaranteed
>by the ARM.
But construction and destruction cannot be mirrors.
That is the problem.
X() : y1(new Y) {}
The compiler cant possibly know whether to delete y1 or not.
Only the programmer knows, and the only place this can be
specified is in the body of the destructor.
Therefore the whole destructor must be invoked for a partially
constructed object.
>> You cant invoke the destructor body for the
>>class because it assumes the class has been completely
>>constructed, but it hasnt been,
>
>As I said above, the ARM guarantees that this won't happen.
The ARM is irrelevant: what I'm trying to figure out
is how it can be made to work AT ALL. Then the ARM will be
changed to that (or some other sufficient scheme).
>Yes, you are right, it's not good enough to just invoke the
>destructor for pointers, since the destructor for pointers is
>a no-op, not a delete.
It had better not be a delete either: only I know
if I'm creating a new object or initialising the pointer
to an existing one.
>Nevertheless, that is all that will happen, according to the ARM.
So the ARM specification is no good.
>Moral: either
> (a) Don't use raw pointers as members. Use a smart pointer class instead.
Might have to. Mm. Have to put an 'owner' flag in it too,
so it knows whether to delete the object on destruction or not.
> (b) Have the constructor catch exceptions if it gets interrupted
> part-way through, and clean up after itself.
Yes, but you cant do that while initialising things with
mem-initialisers.
>
>> That leaves it up to the programmer to destroy
>>at least the partically constructed main subobject.
>
>Not for members with destructors, including smart pointers - this is the
>compilers job. Only for raw pointers is it the programmers job,
>in which case the programmers *mustn't* use mem-initialisers for any
>but the first raw pointer member, in order to be able to catch exceptions
>and clean up properly.
Yes. Thats why smart pointers work: they only have ONE
pointer member :-)
>
>With a smart pointer class it's a lot simpler:
>
> class Y {
> Ptr<X> x1, x2, x3;
> Y() : x1(new X), x2(new X), x3(new X);
> ~Y() { delete x3; delete x2; delete x1; }
> };
>
>No. The compiler should not invoke the main object's destructor,
>because the destructor must only be called on a completely constructed
>object.
Who says? We could change that easily. The alternative
is to use smart pointers everwhere for everything.
>Instead the constructor must catch exceptions at each of the synch
>points, and do the cleanup itself. (It seems easier to avoid synch-points
>in the first place by using a smart pointer class.)
Conclusion: in general, without special techniques,
raw pointers can be used only if they are stored in smart
pointer objects. This applies everywhere: in objects,
on the stack.
Comment: exceptions, especially in 'new' will
break almost all existing code. Its a totally new language.
Why am I beginning to feel having 'new' throw an exception
is a bit problematic?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Tue, 27 Apr 1993 21:46:52 GMT Raw View
In article <hopps.735836137@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>As I said before, if the allocation succeeds but the constructor fails,
>who deletes the memory for the object itself? Can't be the exception
>handler because it cannot know the address of the object. Can't be the
>object because it won't know the memory is dynamic vs automatic or static.
>Has to be operator new.
No, it has to be 'keyword new'. Can we use this phrase to
refer to an expression:
new X;
because 'operator new' is the thing operator new(size_t) which
is different.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Wed, 28 Apr 1993 04:07:46 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>In article <9311802.24596@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>>
>>>fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>
>>Clearly it [the ARM] implies that the destructor for
>>the whole object will *not* be executed.
>
> Yes, and so the ARM is wrong, because it HAS to be.
>
> The destructor for a pointer does nothing, yet the attached
> storage must be deleted. If the pointer is initialised
> in a mem-initialiser there is no *other* way that I can
> see for the object to be properly destroyed.
Well, the first one could be destroyed by a 'catch' in the constructor.
Of course this only works for the first one.
But why do you *need* to initialize pointers (as opposed to smart pointers)
in a mem-initialiser? Answer: you don't.
>>> If the object is only partially constructed, the best or worst
>>>one could hope for is that the object is partially destructed.
>>
>>Yes. That is sufficient though, so long as the partial destruction
>>exactly mirrors the partial construction, as appears is guaranteed
>>by the ARM.
>
> But construction and destruction cannot be mirrors.
>That is the problem.
If the exception occurs during evaluation of a mem-initializer, then
construction and destruction can be exact mirrors. If the exception
occurs during the execution of the block of code for a constructor,
then the corresponding destructor will not be executed, but the
constructor can catch exceptions and do appropriate cleanup.
> X() : y1(new Y) {}
>
>The compiler cant possibly know whether to delete y1 or not.
>Only the programmer knows, and the only place this can be
>specified is in the body of the destructor.
That's not true.
The programmer can specify it in the type of y1, by making y1 a Ptr<X>
instead of an X*.
Alternatively the programmer can specify it the destructor AND
also (perhaps) in a catch(...) statement in the constructor.
>Therefore the whole destructor must be invoked for a partially
>constructed object.
No, your false premises lead you to a false conclusion ;-)
Besides, the whole idea of a destructor being invoked on an object which
hasn't been properly constructed is wrong.
>>Yes, you are right, it's not good enough to just invoke the
>>destructor for pointers, since the destructor for pointers is
>>a no-op, not a delete.
>
>>Nevertheless, that is all that will happen, according to the ARM.
>
> So the ARM specification is no good.
No, the ARM specification is OK, though admittedly we may indeed have to
rewrite all our code to make it work. 1/2 :-)
>>Moral: either
>> (a) Don't use raw pointers as members. Use a smart pointer class instead.
>
> Might have to. Mm. Have to put an 'owner' flag in it too,
>so it knows whether to delete the object on destruction or not.
If you don't own the object pointed to, then you don't need to use
a smart pointer class. In fact you don't have to worry about deallocation
at all.
But it does make sense to have more than one smart pointer class, eg.
you might want to have reference counted pointers, pointers to constants,
reference counted pointers to constants, etc.
>> (b) Have the constructor catch exceptions if it gets interrupted
>> part-way through, and clean up after itself.
>
> Yes, but you cant do that while initialising things with
>mem-initialisers.
But you don't *need* to do that if you are initialising things with
mem-initialisers, unless the mem-initializers are for pointers,
in which case you don't need to use mem-initializers, you can use
assignments in the constructor instead.
Let me state this again: if the member has a destructor, then the
destructor for that member handles the clean-up, and the compiler ensures
that the destructor for that member will be invoked. If the member doesn't
have a destructor, but clean-up is required (eg. pointers to dynamically
allocated objects), then the programmer must handle the cleanup. This
will be a pain, but there's no easy way of fixing this by changing the
language (well, other than by adding garbage collection :-). The situation
can be avoided by using Ptr<X> instead of X*.
>>> That leaves it up to the programmer to destroy
>>>at least the partically constructed main subobject.
>>
>>Not for members with destructors, including smart pointers - this is the
>>compilers job. Only for raw pointers is it the programmers job,
>>in which case the programmers *mustn't* use mem-initialisers for any
>>but the first raw pointer member, in order to be able to catch exceptions
>>and clean up properly.
>
> Yes. Thats why smart pointers work: they only have ONE
>pointer member :-)
:-)
> We could change that [the ARM] easily. The alternative
>is to use smart pointers everwhere for everything.
I might point out that smart pointers have other benefits, too:
they mean you don't have to write copy-constructors or assignment
operators, because the default ones actually work right. Eg.
consider the concise
class Y {
Ptr<X> x1, x2, x3;
public:
Y() : x1(new X), x2(new X), x3(new X);
};
versus the horrificly verbose
class Y {
X *x1, *x2, *x3;
public:
X(); // very ugly, I defined this in my last post
~X(); // normal, and I defined this in my last post too
// big and ugly copy constructor
X(const X &x) {
x1 = new X(*x.x1);
try {
x2 = new X(*x.x2);
} catch (...) { delete x1; throw; }
try {
x3 = new X(*x.x3);
} catch (...) { delete x2; delete x1; throw; }
}
// big and tricky assignment operator
// Beware: what happens if we get an exception here?
X& operator=(const X&x) {
if (this == &x) return *this;
delete x1;
x1 = new X(*x.x1);
delete x2;
x2 = new X(*x.x2);
delete x3;
x3 = new X(*x.x3);
return *this;
}
The smartptr version is even an improvement on the non-exception-safe
raw pointer version.
> Conclusion: in general, without special techniques,
>raw pointers can be used only if they are stored in smart
>pointer objects. This applies everywhere: in objects,
>on the stack.
>
> Comment: exceptions, especially in 'new' will
>break almost all existing code. Its a totally new language.
>
> Why am I beginning to feel having 'new' throw an exception
>is a bit problematic?
It's not 'new' throwing an exception, it's simply the fact that
exceptions can occur. It doesn't make any difference whether
new returns 0 or throws an exception or calls abort() when there's
no memory left, the problem arises because constructors can perform
operatoions, eg. input/output, which may throw exceptions.
For example
X::X() { cout << "Constructor called\n"; }
might very well raise an exception NFS_TIMEOUT if you redirect stdout
to an NFS-mounted file.
But yes C++&E (C++ with exceptions) is almost a totally new langauge.
The difference between C++&E and C++ is considerably more than the
difference between C++&GC and C++.
On the other hand C++ with exceptions *and* garbage collection is
perhaps more similar to current C++ than C++ with exceptions alone is.
(In mathematical notation,
| C++&E&GC - C++ | < | C++&E - C++ |
:-)
--
Fergus Henderson fjh@munta.cs.mu.OZ.AU
This .signature virus is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!
Author: jimad@microsoft.com (Jim Adcock)
Date: 26 Apr 93 17:58:49 GMT Raw View
In article <735511228snx@dspcproj.demon.co.uk> philr@dspcproj.demon.co.uk writes:
|This is what I think should happen ...
|
|There is no such thing as a partialy-constructed object, It is
|either constructed or it dosn't exist. (If it is constructed then
|it is safe and stable so it can be deleted.) As greg said it is up to
|the constructor to make sure anything it allocates is deleted
|before it throws an exception. It should catch any exception thrown
|by its constituent parts and then tidy up and throw a new
|exception. The new operator should then catch this exception and
|discard any memory allocated for the object. Finaly new throws
|the exception which is caught by your code. You don't need to do
|a delete f because f never got assigned.
ARM 15.3c disagrees that there is no such thing as a partially-constructed
object:
"An object that is partially constructed will have destructors executed only
for its fully constructed sub-objects."
|> I can see no way around this problem except for the *language definition*
|> to require the implementation to deallocate the storage if the exception
|> happens during construction. The ARM does not seem to address this
|> question; it only talks about exceptions during the construction of
|> automatic objects.
Given the ARM 15.3c statement it would seem that either every object
has to maintain a record of which of its sub-objects are constructed --
which would be onerous -- or alternately the record of what subobjects
are constructed or not are kept local to the constructor[s] -- in which
case a partially constructed new object or array can never be returned
-- because at that point in time the program would have no record of
what sub-objects are constructed or not. My conclusion thus is that no
partially constructed new'ed object nor array can be returned. Either
all of the constructor[s] succeed, or the new fails, the partially
constructed objects are destructed, the allocation is deallocated, and
null is returned for the new attempt. Complete success, or complete
failure, nothing in between.
Author: vinoski@apollo.hp.com (Steve Vinoski)
Date: Wed, 28 Apr 1993 15:45:08 GMT Raw View
Please see "A Portable Implementation of C++ Exception Handling" by
Don Cameron, Paul Faust, Dmitry Lenkov, and Michey Mehta in the 1992
Usenix C++ Conference proceedings. It describes HP's implementation
of C++ exceptions. In section 5, they describe object cleanup, and
they specifically mention partially constructed dynamically allocated
objects:
"In addition to cleaning up the object, memory allocated for the
object must be deallocated."
See also "Issues related to Exception Handling in C++ - Issue 2" by
Peggy Ellis in the post-Boston X3J16 mailing (ANSI doc number
X3J16/92-0120, ISO doc number WG21/N0197). It consists of 65 pages of
email discussion of holes in the exceptions specification between
Martin Carroll, Andy Koenig, and Bjarne Stroustrup of Bell Labs, Peggy
Ellis, Glen McCluskey, and Jon Shopiro of USL, and Paul Faust and
Michey Mehta of HP. In particular, issue 3.5 in the document
questions whether a partially constructed heap object should be
cleaned up and its memory freed on an exception. The resolution among
those involved was that yes, a partially constructed heap object
should be cleaned up and its memory freed on an exception. Note that
this does not mean that this is the final resolution of the standards
committee, but it does show that the issues are being addressed.
--steve
Steve Vinoski (508)436-5904 vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824 These are my opinions.
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Wed, 28 Apr 93 18:50:04 GMT Raw View
Will somebody on the standards committe respond if they're reading
the thread on exceptions? I'd like to know the thoughts of those
who are "in charge."
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 28 Apr 93 15:35:32 GMT Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
: In article <9311802.24596@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
: >Moral: either
: > (a) Don't use raw pointers as members. Use a smart pointer class instead.
: Might have to. Mm. Have to put an 'owner' flag in it too,
: so it knows whether to delete the object on destruction or not.
No. "Non-owning" pointers can be raw pointers. "Owning" pointers
must be smart pointers, since allocating dynamic memory is
"resource acquisition", which (according to the ARM, CPL2 and other books)
should be modelled as "object initialization".
: > (b) Have the constructor catch exceptions if it gets interrupted
: > part-way through, and clean up after itself.
: Yes, but you cant do that while initialising things with
: mem-initialisers.
Don't use mem-initializers to set pointer members to dynamically allocated
objects. If an exception happens when initializing a pointer-member,
the pointer member will contain garbage (since it hasn't yet been
initialized). Even if the objects full destructor is invoked (and it
won't be), it will have no way to tell whether a pointer-member is valid
or not.
Instead, assign new'ed objects to pointer-members in the constructor body.
Try/catch can then be used for cleanup purposes.
: >Instead the constructor must catch exceptions at each of the synch
: >points, and do the cleanup itself. (It seems easier to avoid synch-points
: >in the first place by using a smart pointer class.)
: Conclusion: in general, without special techniques,
: raw pointers can be used only if they are stored in smart
: pointer objects. This applies everywhere: in objects,
: on the stack.
Yes, but only for "owning" pointers. I don't think this should come as
a surprise. It follows directly from the principles for exception
handling described in the ARM.
--
-------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-977 75 Lulea, Sweden |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490 |
-------------------------------------------------------------------------
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Thu, 29 Apr 1993 11:27:53 GMT Raw View
In article <9311814.13184@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>
>But why do you *need* to initialize pointers (as opposed to smart pointers)
>in a mem-initialiser? Answer: you don't.
True. But if the pointer is a const, its nicer that casting
away const. However, if you have a *reference* even that wont
work: a reference must be initialised in a mem-initialiser.
You can now argue, and I would agree, that a reference should
not be initialised to a 'new' object:
class X {
Y& y;
X() : y(*new Y) { .. }
};
This could be just deemed not only bad style, but an unusable technique
exactly because the 'new Y' cannot throw an exception the constructor
can catch.
>> But construction and destruction cannot be mirrors.
>>That is the problem.
>
>If the exception occurs during evaluation of a mem-initializer, then
>construction and destruction can be exact mirrors.
Thats clearly not the case for pointers. It may
be *possible* to write code that ensures things are mirrors,
but there is no automatic assurance of this.
For example a 'general' smart pointer class would need
a flag to tell it whether to delete the object it points to
or not.
So I guess the result of this discussion is that
writing exception safe code requires appropriate techniques to be
used. Plenty of existing classes cannot be converted to use
exceptions just by installing a new compiler.
>> X() : y1(new Y) {}
>>
>>The compiler cant possibly know whether to delete y1 or not.
>>Only the programmer knows, and the only place this can be
>>specified is in the body of the destructor.
>
>That's not true.
>The programmer can specify it in the type of y1, by making y1 a Ptr<X>
>instead of an X*.
Yes, but that is not then the same class. In other words,
the effect of the change filters through the entire program,
and is not constrained to rewriting the constructor.
>
>>Therefore the whole destructor must be invoked for a partially
>>constructed object.
>
>No, your false premises lead you to a false conclusion ;-)
Yes. My premise was that somehow pointers could
continued to be used in C++, directly as pointers. I think
you've shown that they cant.
How much code will you have to rewrite?
>Besides, the whole idea of a destructor being invoked on an object which
>hasn't been properly constructed is wrong.
I agree.
>> So the ARM specification is no good.
>
>No, the ARM specification is OK, though admittedly we may indeed have to
>rewrite all our code to make it work. 1/2 :-)
Yes. But one of the committee's aims is to avoid breaking
existing code. If in my code I have:
f() {
X* x = new X;
if(!x) { do somethiong }
}
that code is now broken if 'new X' might throw an exception.
Turning off the exception throwing is no answer: the problem
is to find some sort of migration path from old to new.
If such a path does not exist, it is usually sufficient to
prohibit the extension being added to the language.
>
>>>Moral: either
>>> (a) Don't use raw pointers as members. Use a smart pointer class instead.
>>
>> Might have to. Mm. Have to put an 'owner' flag in it too,
>>so it knows whether to delete the object on destruction or not.
>
>If you don't own the object pointed to, then you don't need to use
>a smart pointer class. In fact you don't have to worry about deallocation
>at all.
>
>But it does make sense to have more than one smart pointer class, eg.
>you might want to have reference counted pointers, pointers to constants,
>reference counted pointers to constants, etc.
Miss the point: I may not know whether I own the object
or not on a whole class basis. I might depend on which
constructor is invoked.
>
>>> (b) Have the constructor catch exceptions if it gets interrupted
>>> part-way through, and clean up after itself.
>>
>> Yes, but you cant do that while initialising things with
>>mem-initialisers.
>
>But you don't *need* to do that if you are initialising things with
>mem-initialisers, unless the mem-initializers are for pointers,
>in which case you don't need to use mem-initializers, you can use
>assignments in the constructor instead.
How about references :-)
>> We could change that [the ARM] easily. The alternative
>>is to use smart pointers everwhere for everything.
>
>I might point out that smart pointers have other benefits, too:
>they mean you don't have to write copy-constructors or assignment
>operators, because the default ones actually work right.
Yes, but they have a disadvantage: they do not
convert to smart pointers to base classes. In other words,
they do not support polymorphism.
Many times one uses pointers instead of actual
objects exactly to support polymorphism.
Um, let me clarify:
f(Ptr<Base>) { .. }
Ptr<Derived> d;
f(d); // error
>It's not 'new' throwing an exception, it's simply the fact that
>exceptions can occur.
No, its specifically 'new' because other exceptions
cannot be thrown by 'old' code. You only get an exception if
you throw one or use a library that supports them, which
you haven't done up till now cause there weren't any :-)
>It doesn't make any difference whether
>new returns 0 or throws an exception or calls abort() when there's
>no memory left, the problem arises because constructors can perform
>operatoions, eg. input/output, which may throw exceptions.
>For example
> X::X() { cout << "Constructor called\n"; }
>might very well raise an exception NFS_TIMEOUT if you redirect stdout
>to an NFS-mounted file.
That would be your fault for using iostream.
But you have no choice really about using 'new'.
>
>But yes C++&E (C++ with exceptions) is almost a totally new langauge.
>The difference between C++&E and C++ is considerably more than the
>difference between C++&GC and C++.
>On the other hand C++ with exceptions *and* garbage collection is
>perhaps more similar to current C++ than C++ with exceptions alone is.
>(In mathematical notation,
> | C++&E&GC - C++ | < | C++&E - C++ |
>:-)
Thats interesting. Why?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Thu, 29 Apr 93 15:48:06 GMT Raw View
vinoski@apollo.hp.com (Steve Vinoski) writes:
>Please see "A Portable Implementation of C++ Exception Handling" by
>Don Cameron, Paul Faust, Dmitry Lenkov, and Michey Mehta in the 1992
>Usenix C++ Conference proceedings. It describes HP's implementation
>of C++ exceptions. In section 5, they describe object cleanup, and
>they specifically mention partially constructed dynamically allocated
>objects:
>"In addition to cleaning up the object, memory allocated for the
>object must be deallocated."
Am I correct in assuming that this admonition is directed
at the implementor of "new" rather than the user of "new?"
>See also "Issues related to Exception Handling in C++ - Issue 2" by
>Peggy Ellis in the post-Boston X3J16 mailing (ANSI doc number
>X3J16/92-0120, ISO doc number WG21/N0197). It consists of 65 pages of
>email discussion of holes in the exceptions specification between
>Martin Carroll, Andy Koenig, and Bjarne Stroustrup of Bell Labs, Peggy
>Ellis, Glen McCluskey, and Jon Shopiro of USL, and Paul Faust and
>Michey Mehta of HP. In particular, issue 3.5 in the document
>questions whether a partially constructed heap object should be
>cleaned up and its memory freed on an exception.
Again, am I correct in assuming that this admonition is directed
at the implementor of C++ rather than the user?
> The resolution among
>those involved was that yes, a partially constructed heap object
>should be cleaned up and its memory freed on an exception. Note that
>this does not mean that this is the final resolution of the standards
>committee, but it does show that the issues are being addressed.
Thank you. All this IS reassuring.
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Thu, 29 Apr 1993 17:15:35 GMT Raw View
In article <hopps.736022561@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>Will somebody on the standards committe respond if they're reading
>the thread on exceptions? I'd like to know the thoughts of those
>who are "in charge."
I posted your original (numbered) question/fact posting
to the library reflector of the committee, so that group at least
is aware of this discussion.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd, CSERVE:10236.1703
6 MacKay St ASHFIELD, Mem: SA IT/9/22,SC22/WG21
NSW 2131, AUSTRALIA
Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Thu, 29 Apr 1993 19:35:45 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
> So I guess the result of this discussion is that
>writing exception safe code requires appropriate techniques to be
>used. Plenty of existing classes cannot be converted to use
>exceptions just by installing a new compiler.
Sure they can, you just need to make sure that your compiler supports
garbage collection in addition to exception handling ;-)
That was the point I was hinting at when I said:
>>On the other hand C++ with exceptions *and* garbage collection is
>>perhaps more similar to current C++ than C++ with exceptions alone is.
>
> Thats interesting. Why?
Because in C++ with garbage collection and exceptions, you don't
have to worry about memory leaks when an exception is called.
You *don't* have to modify all that code like
foo::foo(new X, new Y, *new Z) {}
because you can let the garbage collector worry about recovering lost memory.
--
Fergus Henderson This .signature virus might be
fjh@munta.cs.mu.OZ.AU getting old, but you still can't
consistently believe it unless you
Linux: Choice of a GNU Generation copy it to your own .signature file!
Author: vinoski@apollo.hp.com (Steve Vinoski)
Date: Thu, 29 Apr 1993 21:41:16 GMT Raw View
In article <hopps.736097827@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>vinoski@apollo.hp.com (Steve Vinoski) writes:
>
>>"In addition to cleaning up the object, memory allocated for the
>>object must be deallocated."
>
>Am I correct in assuming that this admonition is directed
>at the implementor of "new" rather than the user of "new?"
>
[ text elided ]
>
>Again, am I correct in assuming that this admonition is directed
>at the implementor of C++ rather than the user?
Yes to both questions, sorry for the lack of clarity.
--steve
Steve Vinoski (508)436-5904 vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824 These are my opinions.
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Fri, 16 Apr 93 16:40:10 GMT Raw View
I am wondering about the progress toward the support of exceptions
both in the standard and in existing c++ compilers.
I also have several concerns regarding the use of exceptions at all.
First, let me say that they seem generally like a useful idea. Given
certain language constructs, c++ provides no other good way of finding
out when something didn't work (e.g. constructor failure, overloaded
operator failure, etc.)
As the standard is being worked out, I'm hoping that several things are
being considered:
* What happens if an exception is thrown from a thread different
from the one in which the most recent try block exists?
* If exceptions are thrown by signal handlers or by other functions
which execute asynchronously, it makes it virtually impossible
to write strictly correct software. This is because one lives in
the presence of "lightning bolts" which can come out of nowhere and
steal execution from the currently executing function.
Are there any c++ compilers that presently support exceptions as
proposed in the ARM?
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: bs@alice.att.com (Bjarne Stroustrup)
Date: 17 Apr 93 20:53:47 GMT Raw View
hopps@yellow.mmm.com (Kevin J Hopps @ 3M - St. Paul, MN 55144-1000 US) writes
> I am wondering about the progress toward the support of exceptions
> both in the standard and in existing c++ compilers.
Exceptions as described in the ARM and in ``The C++ Programming Language
(2nd edition)'' was voted into the language by the standard committee.
> I also have several concerns regarding the use of exceptions at all.
> First, let me say that they seem generally like a useful idea. Given
> certain language constructs, c++ provides no other good way of finding
> out when something didn't work (e.g. constructor failure, overloaded
> operator failure, etc.)
Exactly. That is one reason they're there.
> As the standard is being worked out, I'm hoping that several things are
> being considered:
> * What happens if an exception is thrown from a thread different
> from the one in which the most recent try block exists?
That is a bit hard to say because C++ doesn't support threads as a language
construct. This implies that the exception behavior is a property of the
threads mechanism and not of C++. My conclusion when I looked at that
problem last was that the best behavior would be for an exception ``trying
to leave its thread'' is caught and turned into some sort of ``exceptional
thread termination exception'' in the parent thread. Please note, howver,
that there are many notions of concurrency and some don't have a notion of
``parent thread,''
> * If exceptions are thrown by signal handlers or by other functions
> which execute asynchronously, it makes it virtually impossible
> to write strictly correct software. This is because one lives in
> the presence of "lightning bolts" which can come out of nowhere and
> steal execution from the currently executing function.
Under ISO C, signal handler can't even call functions. The intent of
the C++ exception handling mechanism is to handle synchronous exceptions
only. This implies that you can't throw an exception in a signal handler
unless your system happens to provide much stronger guarantees than ISO C
provides and that ISO C++ will provide.
> Are there any c++ compilers that presently support exceptions as
> proposed in the ARM?
IBM, HP, and I think Watcom are shipping implementations supporting
exceptions. There may be more.
In addition, there are several experimental implementations.
- Bjarne
Author: bill@amber.csd.harris.com (Bill Leonard)
Date: 18 Apr 1993 14:43:54 GMT Raw View
In article <25312@alice.att.com>, bs@alice.att.com (Bjarne Stroustrup) writes:
> Exceptions as described in the ARM and in ``The C++ Programming Language
> (2nd edition)'' was voted into the language by the standard committee.
I have been wondering how exceptions will interact with calls to new
and delete. In particular, consider the following example:
class Foo {
public:
Foo(int a) ;
...
} ;
void
do_something() {
try {
Foo * f = new Foo(3) ;
}
catch(...) {
}
}
If 'new' is unable to allocate storage for f, then I would expect an
exception to be thrown and Foo::Foo not be called.
If, however, an exception is thrown during execution of Foo::Foo, will the
storage allocated by 'new' be deallocated? I would hope so, as it would be
nearly impossible for the catch clause to tell whether the exception
occurred during allocation or during construction, and in any case it is
impossible for the catch clause to access variable 'f' in order to
deallocate the storage.
--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL 33309
bill@ssd.csd.harris.com
These opinions and statements are my own and do not reflect the opinions or
positions of Harris Corporation.
------------------------------------------------------------------------------
What this country needs is a bumper as durable as a bumper sticker.
------------------------------------------------------------------------------
Author: jfc@athena.mit.edu (John F Carr)
Date: 18 Apr 1993 15:36:12 GMT Raw View
In article <1qrpfa$prk@travis.csd.harris.com>
bill@amber.csd.harris.com (Bill Leonard) writes:
>If, however, an exception is thrown during execution of Foo::Foo, will the
>storage allocated by 'new' be deallocated?
The problem is worse than that. A half constructed object may leave the
program's data structures inconsistent. Consider a package which uses
reference counted structures. Interrupting a constructor can leave the
reference count too high (memory leak) or too low (pointers to invalid
structures).
Exceptions are an incompatible language extension: old code may not work
properly in the presence of exceptions, even when the code neither catches
nor throws exceptions. Constructors which may be interrupted need to be
modified to protect critical code.
The existence of setjmp/longjmp in C could cause problems similar to
exceptions: code may now be interrupted in the middle of a sequence that
was previously uninterruptible. In practice, setjmp and longjmp are
sufficiently rare in C code that this isn't a problem. C++ encourages
more complex and vulnerable data structures, and exceptions will be used
much more than setjmp/longjmp.
There is a conceptually infinite number of exceptions that might occur, and
in general not all exception types will be in scope, so you can't solve this
problem with brute force (catch all possible exceptions). One could throw
pointers to objects and have constructors catch (void *), but this
complicates exception handlers (which now need to explicitly delete the
exception object) and causes the global memory allocator to be called at
a bad time.
This is a problem that can be solved by good software engineering: write
interface descriptions for all classes and _use them_. When implementing a
class, plan for exceptions and document when they are not safe.
With care, introducing exceptions to C++ should turn out to be no more
harmful than allowing C compilers to store variables in registers in
functions that call setjmp. But some programs will probably stop working.
--
John Carr (jfc@athena.mit.edu)
Author: jbn@lulea.trab.se (Johan Bengtsson)
Date: 18 Apr 93 12:22:34 GMT Raw View
Kevin J Hopps (hopps@yellow.mmm.com) wrote:
: As the standard is being worked out, I'm hoping that several things are
: being considered:
: * What happens if an exception is thrown from a thread different
: from the one in which the most recent try block exists?
: * If exceptions are thrown by signal handlers or by other functions
: which execute asynchronously, it makes it virtually impossible
: to write strictly correct software. This is because one lives in
: the presence of "lightning bolts" which can come out of nowhere and
: steal execution from the currently executing function.
Your first item and the second item is really the one and same
(assuming you are only interested in _unhandled_ exceptions from other
threads). How (and when) are asynchronuously occuring exceptions propagated
to an executing process? Polling at the beginning of each try and catch
block might be semantically acceptable, but not so good from a
performance point of view. Perhaps this should be left as an implementation
dependent issue? Some implementations might use semaphores, and require
interested execution threads to check those.
Note that OODBMS that allow C++ code to execute against persistent data
may in principle generate "lightning bolts" at any time, since as litte
as a single-byte read may cause an error with the DB, for example
that very byte may well have caused a deadlock situation with another
executing process.
I agree that it is difficult to write correct software under these
circumstances. The only viable solution seems to be strict adherance
to the principle "resource acquisition is initialization". By representing
all acquired resources as automatic objects, the compiler takes care
of any cleanup needs. Note that the constructors and destructors of
resource classes you create will have to be coded very carefully, so
that they can endure "lightning". This will hopefully be a very small
part of the code.
--
-------------------------------------------------------------------------
| Johan Bengtsson, Telia Research AB, Aurorum 6, S-977 75 Lulea, Sweden |
| Johan.Bengtsson@lulea.trab.se; Voice:(+46)92075471; Fax:(+46)92075490 |
-------------------------------------------------------------------------
Author: jurlwin@gandalf.UMCS.Maine.EDU (Jeff Urlwin)
Date: Sun, 18 Apr 1993 22:43:22 GMT Raw View
In article <hopps.734977377@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>Are there any c++ compilers that presently support exceptions as
>proposed in the ARM?
IBM's CSet++ for OS/2.
Jeff
--
--------------------------------------
[ jurlwin@gandalf.umcs.maine.edu ]
[ Friends don't let friends do Windows... ]
Author: bill@amber.csd.harris.com (Bill Leonard)
Date: 19 Apr 1993 13:24:43 GMT Raw View
In article <1qrshcINNka6@senator-bedfellow.MIT.EDU>, jfc@athena.mit.edu (John F Carr) writes:
>
> The problem is worse than that. A half constructed object may leave the
> program's data structures inconsistent. Consider a package which uses
> reference counted structures. Interrupting a constructor can leave the
> reference count too high (memory leak) or too low (pointers to invalid
> structures).
Well, of course! I never said I expected my code to work unchanged. What
I'm concerned about is the situation I can't possibly code for: an
exception occurs during a 'new' and I can't tell whether the memory has
been allocated or not, and if it has, I have no way to reclaim it.
At least, that's what I know so far. What I'm looking for is a description
of exactly what I can rely on the implementation doing for me, and some
guidelines on how I *should* code for this situation.
> Exceptions are an incompatible language extension: old code may not work
> properly in the presence of exceptions, even when the code neither catches
> nor throws exceptions. Constructors which may be interrupted need to be
> modified to protect critical code.
Sure. But before I can modify it, I need to know what the semantics are.
--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL 33309
bill@ssd.csd.harris.com
These opinions and statements are my own and do not reflect the opinions or
positions of Harris Corporation.
------------------------------------------------------------------------------
What this country needs is a bumper as durable as a bumper sticker.
------------------------------------------------------------------------------
Author: gregw@cssc-syd.tansu.com.au (Greg Wilkins)
Date: 20 Apr 1993 04:08:00 GMT Raw View
In article mtr@travis.csd.harris.com, bill@amber.csd.harris.com (Bill Leonard) writes:
>In article <1qrshcINNka6@senator-bedfellow.MIT.EDU>, jfc@athena.mit.edu (John F Carr) writes:
>>
>> The problem is worse than that. A half constructed object may leave the
>> program's data structures inconsistent.
>At least, that's what I know so far. What I'm looking for is a description
>of exactly what I can rely on the implementation doing for me, and some
>guidelines on how I *should* code for this situation.
>
>Sure. But before I can modify it, I need to know what the semantics are.
>
As Bjarne said, the exception mechansim as described in
"The C++ programming language - second edition", has been accepted. This is the description that you are looking for, together with most of the semantics.
Furthermore the book gives a good introduction on the guidlines you need to follow
to avoid resource allocation problems, which I can sum up in two simple principles:
1) If your constructor allocates resources, then it must free them a) in the
destructor and b) before throwing an exception in the constructor.
2) If the constructor is complex, and you cannot be sure that you will be able to
free all resources before an exception is thrown, then don't allocate
resources: instead construct automatic objects which allocate the resources, whose
destructors will be called for all exception and thus free those resources.
Use principle 2) to decompose your constructors until they are simple enough
to apply principle 1) -> Which probably is good practise with or without
exceptions!
-gregw
Author: bill@amber.csd.harris.com (Bill Leonard)
Date: 20 Apr 1993 15:18:10 GMT Raw View
In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
> As Bjarne said, the exception mechansim as described in "The C++
> programming language - second edition", has been accepted. This is the
> description that you are looking for, together with most of the
> semantics.
I take it, then, that this *isn't* the same as the one in the ARM? The
one in the ARM does not answer my question.
> Furthermore the book gives a good introduction on the guidlines you need
> to follow to avoid resource allocation problems, which I can sum up in
> two simple principles:
>
> 1) If your constructor allocates resources, then it must free them a) in
> the destructor and b) before throwing an exception in the constructor.
>
> 2) If the constructor is complex, and you cannot be sure that you will be
> able to free all resources before an exception is thrown, then don't
> allocate resources: instead construct automatic objects which allocate
> the resources, whose destructors will be called for all exception and
> thus free those resources.
Yep, those are nice principles, but they don't address my question! The
question is not what to do when a constructor allocates resources. The
question is, what do I do when I allocate an object (via new), and I get an
exception, possibly during allocation and possibly during construction?
For instance,
class Foo {
public:
Foo() {
...
} ;
...
}
void
sub() {
...
try {
Foo * f = new Foo(/* arguments */) ;
}
catch (...) {
// What can I do here? I don't know if the Foo was allocated
// or not. If it was, then it was the constructor that failed,
// but I don't know whether the storage was deallocated or not.
}
}
How can I decompose this further? The allocation and construction happen
as part of the 'new' operation. Even if I put the 'new' operation into an
automatic object, how will that object know when the exception occurred?
It will still not know whether to delete 'f' or not. Furthermore, if I
execute "delete f", then I will call the destructor for Foo on a
partially-constructed Foo object.
A secondary question is whether the pointer variable 'f' will have been
defined with the pointer to the allocated storage or not. Given that the
storage has not been fully constructed, it would make sense not to fill in
'f', but then you have no access to the allocated but unconstructed storage
in order to delete it.
I can see no way around this problem except for the *language definition*
to require the implementation to deallocate the storage if the exception
happens during construction. The ARM does not seem to address this
question; it only talks about exceptions during the construction of
automatic objects.
--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL 33309
bill@ssd.csd.harris.com
These opinions and statements are my own and do not reflect the opinions or
positions of Harris Corporation.
------------------------------------------------------------------------------
What this country needs is a bumper as durable as a bumper sticker.
------------------------------------------------------------------------------
Author: krc@wam.umd.edu (Kevin R. Coombes)
Date: Tue, 20 Apr 1993 19:09:30 GMT Raw View
In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>[stuff omitted] The
>question is not what to do when a constructor allocates resources. The
>question is, what do I do when I allocate an object (via new), and I get an
>exception, possibly during allocation and possibly during construction?
>For instance,
>
>void
>sub() {
> ...
> try {
> Foo * f = new Foo(/* arguments */) ;
> }
> catch (...) {
> // What can I do here? I don't know if the Foo was allocated
> // or not. If it was, then it was the constructor that failed,
> // but I don't know whether the storage was deallocated or not.
> }
>}
>
>How can I decompose this further? The allocation and construction happen
>as part of the 'new' operation. Even if I put the 'new' operation into an
>automatic object, how will that object know when the exception occurred?
>It will still not know whether to delete 'f' or not. Furthermore, if I
>execute "delete f", then I will call the destructor for Foo on a
>partially-constructed Foo object.
>
I'm not sure I see the problem. The call to new does two things: first,
it allocates a certain amount of memory. Then it uses the constructor
to convert the raw memory into a Foo. If the allocation fails, then
new calls a new_handler, if one exists. If not, it returns zero. You get
to decide what the new_handler does. If it throws an exception, then
you can choose the type of the exception: for example,
throw AllocationFailure;
inside the new_handler. Then your try block looks like
try {
Foo *f = new Foo;
// use f
delete f;
} catch (AllocationFailure) {
// now you know allocation failed.
// you can try to make more memory available
// and restart the operation.
} catch(FooConstructorFailure) {
// now you know the constructor failed.
// fully constructed subobjects have been destroyed
// already.
}
>A secondary question is whether the pointer variable 'f' will have been
>defined with the pointer to the allocated storage or not. Given that the
>storage has not been fully constructed, it would make sense not to fill in
>'f', but then you have no access to the allocated but unconstructed storage
>in order to delete it.
Now, I agree that this is a problem. If memory has been allocated from the
heap, but not constructed properly, and if you lose all pointers to it,
then your program has a memory leak. Can somebody comment definitively
on this question? Does the language guarantee that execptions during
the construction of a new object do not cause memory leaks?
Kevin Coombes <krc@math.umd.edu>
Author: danm@squam.banyan.com (Daniel W. Muller)
Date: Wed, 21 Apr 1993 14:26:51 GMT Raw View
In article <1993Apr20.190930.19406@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
> In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
> >A secondary question is whether the pointer variable 'f' will have been
> >defined with the pointer to the allocated storage or not. Given that the
> >storage has not been fully constructed, it would make sense not to fill in
> >'f', but then you have no access to the allocated but unconstructed storage
> >in order to delete it.
>
> Now, I agree that this is a problem. If memory has been allocated from the
> heap, but not constructed properly, and if you lose all pointers to it,
> then your program has a memory leak. Can somebody comment definitively
> on this question? Does the language guarantee that execptions during
> the construction of a new object do not cause memory leaks?
Even if you don't lose all pointers to it, you've still got a
problem. You can't call delete, because this will invoke a
destructor on an object that was never properly constructed. "The C++
Programming Language, Second Edition", does not seem to clarify this
point. Perhaps the intent is to have the constructor ALWAYS leave
the object in a state that a destructor can deal with. But this can
become very complex in the presence of even simple inheritance - what
if an exception is thrown by a base class constructor, and is not
handled by a derived class constructor? It seems that the easiest
solution would be to have the run-time system deallocate the storage
whenever an exception is thrown out of a constructor which was
called via new.
--
Dan Muller (danm@banyan.com) - Banyan Systems Inc.
Author: cok@acadia.Kodak.COM (David Cok)
Date: 22 Apr 93 12:04:03 GMT Raw View
In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>void
>sub() {
> ...
> try {
> Foo * f = new Foo(/* arguments */) ;
> }
> catch (...) {
> // What can I do here? I don't know if the Foo was allocated
> // or not. If it was, then it was the constructor that failed,
> // but I don't know whether the storage was deallocated or not.
> }
>}
>
>How can I decompose this further? The allocation and construction happen
>as part of the 'new' operation. Even if I put the 'new' operation into an
>automatic object, how will that object know when the exception occurred?
>It will still not know whether to delete 'f' or not. Furthermore, if I
>execute "delete f", then I will call the destructor for Foo on a
>partially-constructed Foo object.
>
>A secondary question is whether the pointer variable 'f' will have been
>defined with the pointer to the allocated storage or not. Given that the
>storage has not been fully constructed, it would make sense not to fill in
>'f', but then you have no access to the allocated but unconstructed storage
>in order to delete it.
>
1) To distinguish different modes of failure, catch different exceptions.
That could distinguish between failures to allocate and failures to construct.
2) If a operation (e.g. new X) fails, I would expect that subsequent operations
within the expression would not be executed. Hence in the example above, f
would remain undefined.
3) There still remains the question of whether partially constructed objects
ever surface. I think that a very careful constructor needs to contain its
own try-catch blocks and make sure that it exits either fully constructed or
not at all constructed.
David Cok
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Thu, 22 Apr 93 17:09:46 GMT Raw View
cok@acadia.Kodak.COM (David Cok) writes:
>In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>>void
>>sub() {
>> ...
>> try {
>> Foo * f = new Foo(/* arguments */) ;
>> }
>> catch (...) {
>> // What can I do here? I don't know if the Foo was allocated
>> // or not. If it was, then it was the constructor that failed,
>> // but I don't know whether the storage was deallocated or not.
>> }
>>}
>>
>>How can I decompose this further? The allocation and construction happen
>>as part of the 'new' operation. Even if I put the 'new' operation into an
>>automatic object, how will that object know when the exception occurred?
>>It will still not know whether to delete 'f' or not. Furthermore, if I
>>execute "delete f", then I will call the destructor for Foo on a
>>partially-constructed Foo object.
>>
>>A secondary question is whether the pointer variable 'f' will have been
>>defined with the pointer to the allocated storage or not. Given that the
>>storage has not been fully constructed, it would make sense not to fill in
>>'f', but then you have no access to the allocated but unconstructed storage
>>in order to delete it.
>>
>1) To distinguish different modes of failure, catch different exceptions.
>That could distinguish between failures to allocate and failures to construct.
>2) If a operation (e.g. new X) fails, I would expect that subsequent operations
>within the expression would not be executed. Hence in the example above, f
>would remain undefined.
>3) There still remains the question of whether partially constructed objects
>ever surface. I think that a very careful constructor needs to contain its
>own try-catch blocks and make sure that it exits either fully constructed or
>not at all constructed.
>David Cok
I've been following this thread for a while now. Several people have
dismissed this situation as a non-problem. Is everybody missing the point?
Even if I go through all the trouble of figuring out every different
kind of exception which can be thrown by "new X" today, the implementor
of X may change things tomorrow and break my code. Further, while catching
every different exception which can occur as a result of "new X" does
indeed let me distinguish between failures to allocate and failures to
construct, knowing that doesn't help if the failure was in the
construction and not the allocation. The reason is (and this has
already been stated in a previous posting) that there is no way to
*properly* release the memory acquired by new.
Would somebody please answer the following "Question" items?
Assumption: I call "new X" and an exception is thrown.
Fact: If the failure occured during allocation, there is nothing
that must be "cleaned up". Therefore make the
Assumption: The failure occured after successful allocation, during
construction.
Fact: If a memory leak is to be avoided, the allocated memory must
be released.
Fact: The caller of "new X" never learns where the memory is,
because operator new never returns.
Fact: If a memory leak is to be avoided, operator new must catch
exceptions and release the memory.
Question: When exceptions are implemented, will the default
operator new catch them, release acquired memory and
rethrow them?
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: pathak@mitre.org (Heeren Pathak)
Date: Thu, 22 Apr 1993 20:18:00 GMT Raw View
In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
wrote:
>
> cok@acadia.Kodak.COM (David Cok) writes:
>
> >In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
> >>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
> >>void
> >>sub() {
> >> ...
> >> try {
> >> Foo * f = new Foo(/* arguments */) ;
> >> }
> >> catch (...) {
> >> // What can I do here? I don't know if the Foo was allocated
> >> // or not. If it was, then it was the constructor that failed,
> >> // but I don't know whether the storage was deallocated or not.
> >> }
> >>}
> >>
The above example already has a problem. The varible 'f' is enclosed
within the try block. That means 'f' loses scope outside the try block and
you now have a memory leak.
The correct code block should be...
void sub() {
Foo* f = (Foo*)0;
try {
f = new Foo(/*arguments*/);
}
catch (...) {
delete f;
}
Note: It is okay to 'delete f'. See below.
> Even if I go through all the trouble of figuring out every different
> kind of exception which can be thrown by "new X" today, the implementor
> of X may change things tomorrow and break my code. Further, while catching
> every different exception which can occur as a result of "new X" does
> indeed let me distinguish between failures to allocate and failures to
> construct, knowing that doesn't help if the failure was in the
> construction and not the allocation. The reason is (and this has
> already been stated in a previous posting) that there is no way to
> *properly* release the memory acquired by new.
>
> Would somebody please answer the following "Question" items?
>
> Assumption: I call "new X" and an exception is thrown.
> Fact: If the failure occured during allocation, there is nothing
> that must be "cleaned up". Therefore make the
If new threw the exception, the no problem since 'f' was previous
initialized to null. Since delete null is valid and safe operation,
everything is okay.
> Assumption: The failure occured after successful allocation, during
> construction.
> Fact: If a memory leak is to be avoided, the allocated memory must
> be released.
> Fact: The caller of "new X" never learns where the memory is,
> because operator new never returns.
I believe this is where you are confusing memory allocation with object
construction and thus making a bad assumption. Operator new did complete
successfully so the memory was allocated and 'f' points to this allocated
memory block. The constructor failed so the memory block may not be
initialized correctly. The memory was allocated and performing a delete f
releases the allocated memory.
> Fact: If a memory leak is to be avoided, operator new must catch
> exceptions and release the memory.
> Question: When exceptions are implemented, will the default
> operator new catch them, release acquired memory and
> rethrow them?
-------------------------------------------------------------------------
Heeren Pathak | Millions long for immortality who do
pathak@mitre.org | not know what to do with themselves
Mitre Corporation | on a rainy Sunday afternoon.
(617) 271-7465 | -- Susan Ertz
-------------------------------------------------------------------------
Disclaimer: Mine not Mitre's.
Author: gregw@minotaur.tansu.com.au (Greg Wilkins)
Date: 22 Apr 1993 23:59:54 GMT Raw View
In article 19406@wam.umd.edu, krc@wam.umd.edu (Kevin R. Coombes) writes:
>>A secondary question is whether the pointer variable 'f' will have been
>>defined with the pointer to the allocated storage or not. Given that the
>>storage has not been fully constructed, it would make sense not to fill in
>>'f', but then you have no access to the allocated but unconstructed storage
>>in order to delete it.
>
>Now, I agree that this is a problem. If memory has been allocated from the
>heap, but not constructed properly, and if you lose all pointers to it,
>then your program has a memory leak. Can somebody comment definitively
>on this question? Does the language guarantee that execptions during
>the construction of a new object do not cause memory leaks?
>
Firstly, I think it is impossible to guarantee no memory leaks in C++, unless you
removed simple pointers and/or introduced garbage colection.
Secondly, If you wish to access data in an exception handler which is out of scope,
remember that what is thrown is an object, and arbitrary data can be included in that
object for use by the handler.
Thirdly, if in doubt about how much of an object was constructed, then delete
everything. The delete operation checks for NULL pointers and does not delete
them, so
delete 0;
is legal. Thus if you initializer all your pointers to NULL before starting
allocation, then delete can be called on ALL pointers in the destructor.
-gregw
Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Fri, 23 Apr 93 16:13:53 GMT Raw View
pathak@mitre.org (Heeren Pathak) writes:
>In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
>wrote:
>>
>> cok@acadia.Kodak.COM (David Cok) writes:
>>
>> >In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>> >>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>> >>void
>> >>sub() {
>> >> ...
>> >> try {
>> >> Foo * f = new Foo(/* arguments */) ;
>> >> }
>> >> catch (...) {
>> >> // What can I do here? I don't know if the Foo was allocated
>> >> // or not. If it was, then it was the constructor that failed,
>> >> // but I don't know whether the storage was deallocated or not.
>> >> }
>> >>}
>> >>
>The above example already has a problem. The varible 'f' is enclosed
>within the try block. That means 'f' loses scope outside the try block and
>you now have a memory leak.
>The correct code block should be...
>void sub() {
> Foo* f = (Foo*)0;
> try {
> f = new Foo(/*arguments*/);
> }
> catch (...) {
> delete f;
> }
>Note: It is okay to 'delete f'. See below.
>> Even if I go through all the trouble of figuring out every different
>> kind of exception which can be thrown by "new X" today, the implementor
>> of X may change things tomorrow and break my code. Further, while catching
>> every different exception which can occur as a result of "new X" does
>> indeed let me distinguish between failures to allocate and failures to
>> construct, knowing that doesn't help if the failure was in the
>> construction and not the allocation. The reason is (and this has
>> already been stated in a previous posting) that there is no way to
>> *properly* release the memory acquired by new.
>>
>> Would somebody please answer the following "Question" items?
>>
>> Assumption: I call "new X" and an exception is thrown.
>> Fact: If the failure occured during allocation, there is nothing
>> that must be "cleaned up". Therefore make the
>If new threw the exception, the no problem since 'f' was previous
>initialized to null. Since delete null is valid and safe operation,
>everything is okay.
>> Assumption: The failure occured after successful allocation, during
>> construction.
>> Fact: If a memory leak is to be avoided, the allocated memory must
>> be released.
>> Fact: The caller of "new X" never learns where the memory is,
>> because operator new never returns.
>I believe this is where you are confusing memory allocation with object
>construction and thus making a bad assumption. Operator new did complete
>successfully so the memory was allocated and 'f' points to this allocated
>memory block. The constructor failed so the memory block may not be
>initialized correctly. The memory was allocated and performing a delete f
>releases the allocated memory.
Wrong! If new allocates the memory but the constructor throws the
exception, then the memory is allocated but f does not point to it,
since new doesn't return the pointer. You are correct in your above
statement which says if f is initialized to zero it is safe to delete
it. However, it doesn't point to the memory which was allocated.
My original question still remains:
>> Fact: If a memory leak is to be avoided, operator new must catch
>> exceptions and release the memory.
>> Question: When exceptions are implemented, will the default
>> operator new catch them, release acquired memory and
>> rethrow them?
--
Kevin J. Hopps e-mail: kjhopps@mmm.com
3M Company phone: (612) 737-3300
3M Center, Bldg. 235-3B-16 fax: (612) 737-2700
St. Paul, MN 55144-1000 ** USE kjhopps@mmm.com FOR E-MAIL REPLIES **
Author: g2devi@cdf.toronto.edu (Deviasse Robert N.)
Date: Fri, 23 Apr 1993 18:32:08 GMT Raw View
In article <hopps.735581081@mmm.com> hopps@yellow.mmm.com (Kevin J Hopps) writes:
>pathak@mitre.org (Heeren Pathak) writes:
>
>>In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
>>wrote:
>>>
>>> cok@acadia.Kodak.COM (David Cok) writes:
>>>
>>> >In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
>>> >>In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
>>> >>void
>>> >>sub() {
>>> >> ...
>>> >> try {
>>> >> Foo * f = new Foo(/* arguments */) ;
>>> >> }
>>> >> catch (...) {
>>> >> // What can I do here? I don't know if the Foo was allocated
>>> >> // or not. If it was, then it was the constructor that failed,
>>> >> // but I don't know whether the storage was deallocated or not.
>>> >> }
>>> >>}
>>> >>
>
>>The above example already has a problem. The varible 'f' is enclosed
>>within the try block. That means 'f' loses scope outside the try block and
>>you now have a memory leak.
>
>>The correct code block should be...
>>void sub() {
>> Foo* f = (Foo*)0;
>> try {
>> f = new Foo(/*arguments*/);
>> }
>> catch (...) {
>> delete f;
>> }
>
>>Note: It is okay to 'delete f'. See below.
>
>>> Even if I go through all the trouble of figuring out every different
>>> kind of exception which can be thrown by "new X" today, the implementor
>>> of X may change things tomorrow and break my code. Further, while catching
>>> every different exception which can occur as a result of "new X" does
>>> indeed let me distinguish between failures to allocate and failures to
>>> construct, knowing that doesn't help if the failure was in the
>>> construction and not the allocation. The reason is (and this has
>>> already been stated in a previous posting) that there is no way to
>>> *properly* release the memory acquired by new.
>>>
>>> Would somebody please answer the following "Question" items?
>>>
>>> Assumption: I call "new X" and an exception is thrown.
>>> Fact: If the failure occured during allocation, there is nothing
>>> that must be "cleaned up". Therefore make the
>
>>If new threw the exception, the no problem since 'f' was previous
>>initialized to null. Since delete null is valid and safe operation,
>>everything is okay.
>
>>> Assumption: The failure occured after successful allocation, during
>>> construction.
>>> Fact: If a memory leak is to be avoided, the allocated memory must
>>> be released.
>>> Fact: The caller of "new X" never learns where the memory is,
>>> because operator new never returns.
>
>>I believe this is where you are confusing memory allocation with object
>>construction and thus making a bad assumption. Operator new did complete
>>successfully so the memory was allocated and 'f' points to this allocated
>>memory block. The constructor failed so the memory block may not be
>>initialized correctly. The memory was allocated and performing a delete f
>>releases the allocated memory.
>
>Wrong! If new allocates the memory but the constructor throws the
>exception, then the memory is allocated but f does not point to it,
>since new doesn't return the pointer. You are correct in your above
>statement which says if f is initialized to zero it is safe to delete
>it. However, it doesn't point to the memory which was allocated.
>
>My original question still remains:
Suppose we define the following class:
class ConstructorFailure{
private:
void *ptr;
public:
ConstructorFailure() : ptr(0){}
ConstructorFailure(void *mem) : ptr(mem) {}
operator void*() const { return ptr; }
};
Then if the contructor fails, it can throw the exception: ConstructorFailure(this)
So the above code can be written as:
void sub() {
Foo* f = NULL;
try {
f = new Foo(/*arguments*/);
} catch (ConstructorFailure ptr) {
delete ptr;
} catch (...) {
delete f;
}
}
Take care
Robert
BTW, could someone email me if this posting has gotten out of Ontario?
Thanks.
--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse |"If we have to re-invent the wheel, |
| EMAIL: g2devi@cdf.utoronto.ca | can we at least make it round this time"|
+----------------------------------+------------------------------------------/
Author: cok@acadia.Kodak.COM (David Cok)
Date: Fri, 23 Apr 93 15:28:25 GMT Raw View
In article <pathak-220493150703@virtual.mitre.org> pathak@mitre.org (Heeren Pathak) writes:
>In article <hopps.735494692@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
>wrote:
>>
>>
>> Would somebody please answer the following "Question" items?
>>
>> Assumption: I call "new X" and an exception is thrown.
>> Fact: If the failure occured during allocation, there is nothing
>> that must be "cleaned up". Therefore make the
>> Assumption: The failure occured after successful allocation, during
>> construction.
>> Fact: If a memory leak is to be avoided, the allocated memory must
>> be released.
>> Fact: The caller of "new X" never learns where the memory is,
>> because operator new never returns.
>
>I believe this is where you are confusing memory allocation with object
>construction and thus making a bad assumption. Operator new did complete
>successfully so the memory was allocated and 'f' points to this allocated
>memory block. The constructor failed so the memory block may not be
>initialized correctly. The memory was allocated and performing a delete f
>releases the allocated memory.
>
The expression new X does two things:
calls ::operator new
calls X::X()
If the exception occurs in either part, the result of the expression will be
undefined. If the exception occurs in the constructor, then, as Kevin and I
have said, the programmer has no knowledge of the address of the successfully
allocated memory. Thus to be safe, if an exception is propagated out of the
new expression, it should free the memory; this must be defined by the
language, and arranged by the compiler.
If an exception is propagated out of the constructor, the
constructor must take care of any cleanup, leaving the memory in a
"pre-constructed" state; this must be arranged by the programmer.
David R. Cok
Author: pathak@mitre.org (Heeren Pathak)
Date: Fri, 23 Apr 1993 21:15:48 GMT Raw View
In article <hopps.735581081@mmm.com>, hopps@yellow.mmm.com (Kevin J Hopps)
wrote:
>
> pathak@mitre.org (Heeren Pathak) writes:
>
>The correct code block should be...
>void sub() {
> Foo* f = (Foo*)0;
> try {
> f = new Foo(/*arguments*/);
> }
> catch (...) {
> delete f;
> }
>
> >I believe this is where you are confusing memory allocation with object
> >construction and thus making a bad assumption. Operator new did complete
> >successfully so the memory was allocated and 'f' points to this allocated
> >memory block. The constructor failed so the memory block may not be
> >initialized correctly. The memory was allocated and performing a delete f
> >releases the allocated memory.
>
> Wrong! If new allocates the memory but the constructor throws the
> exception, then the memory is allocated but f does not point to it,
> since new doesn't return the pointer. You are correct in your above
> statement which says if f is initialized to zero it is safe to delete
> it. However, it doesn't point to the memory which was allocated.
>
> My original question still remains:
>
> >> Fact: If a memory leak is to be avoided, operator new must catch
> >> exceptions and release the memory.
> >> Question: When exceptions are implemented, will the default
> >> operator new catch them, release acquired memory and
> >> rethrow them?
I was wrong. I assumed that memory allocation occurs and the returned
pointer is assigned to 'f'. Afterwards, the constructor is called but
either way 'f' either points to nothing or points to the allocated memory.
However, Section 12 of ARM has seems indicate otherwise. It seems to me
that ARM has a loophole that might cause problems when constructors throw
an exception.
Author: jimad@microsoft.com (Jim Adcock)
Date: 23 Apr 93 18:29:24 GMT Raw View
In article <pathak-220493150703@virtual.mitre.org> pathak@mitre.org (Heeren Pathak) writes:
|I believe this is where you are confusing memory allocation with object
|construction and thus making a bad assumption. Operator new did complete
|successfully so the memory was allocated and 'f' points to this allocated
|memory block. The constructor failed so the memory block may not be
|initialized correctly. The memory was allocated and performing a delete f
|releases the allocated memory.
You're assuming that 'f' is pointed at the allocation before the construction
is attempted. I don't think ARM specifies this behavior, but rather leaves
it unspecified [as far as I can find]. The ARM does talk about how
implementation can either call operator new from inside the constructor,
or can have a separate call to operator new before the constructor is called.
The obvious ways of implementing these two approaches would seem to give
two different values for 'f' when an exception happens in the constructor.
I suggest that this problem has been left unspecified to date, and
needs to be specified. Perhaps someone can call out the appropriate
sections in ARM if I've missed something.
Author: philr@dspcproj.demon.co.uk (Phil Reynolds)
Date: Thu, 22 Apr 1993 13:40:28 +0000 Raw View
In article <1r147i$njd@travis.csd.harris.com> bill@amber.csd.harris.com (Bill Leonard) writes:
> In article <1qvsv0$kdd@picasso.cssc-syd.tansu.com.au>, gregw@cssc-syd.tansu.com.au (Greg Wilkins) writes:
[ ... ]
> > 1) If your constructor allocates resources, then it must free them a) in
> > the destructor and b) before throwing an exception in the constructor.
> >
>
> Yep, those are nice principles, but they don't address my question! The
[ ... ]
>
> How can I decompose this further? The allocation and construction happen
> as part of the 'new' operation. Even if I put the 'new' operation into an
> automatic object, how will that object know when the exception occurred?
> It will still not know whether to delete 'f' or not. Furthermore, if I
> execute "delete f", then I will call the destructor for Foo on a
> partially-constructed Foo object.
This is what I think should happen ...
There is no such thing as a partialy-constructed object, It is
either constructed or it dosn't exist. (If it is constructed then
it is safe and stable so it can be deleted.) As greg said it is up to
the constructor to make sure anything it allocates is deleted
before it throws an exception. It should catch any exception thrown
by its constituent parts and then tidy up and throw a new
exception. The new operator should then catch this exception and
discard any memory allocated for the object. Finaly new throws
the exception which is caught by your code. You don't need to do
a delete f because f never got assigned.
I am not sure if this is the case, and I fear it is hidiously
inefficient. I must admit to not having used exceptions in C++,
but I have used setjmp/longjmp macros to simulate them in the
past, and these are just the sort of problems I have come up
with. Which has always left me with removing them from my code
and going back to testing the validity of each object after the
constructor.
>
> A secondary question is whether the pointer variable 'f' will have been
> defined with the pointer to the allocated storage or not. Given that the
> storage has not been fully constructed, it would make sense not to fill in
> 'f', but then you have no access to the allocated but unconstructed storage
> in order to delete it.
>
I think my previous point answers this.
> I can see no way around this problem except for the *language definition*
> to require the implementation to deallocate the storage if the exception
> happens during construction. The ARM does not seem to address this
> question; it only talks about exceptions during the construction of
> automatic objects.
I aggree!
+-------------------------------+---------------------------+
| Phil Reynolds | |
+-------------------------------+---------------------------+
| PC Projects | I just *work* here. |
| Datastream International Ltd. | I don't speak for them. |
| philr@dspcproj.demon.co.uk | If our opinions coincide, |
| | I must be wrong! |
+-------------------------------+---------------------------+