Topic: exceptions, ctors, dtors, dynamic mem


Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Fri, 8 Oct 93 18:08:26 GMT
Raw View
I have been talking with a compiler manufacturer regarding the
throwing of exceptions from constructors when the objects are
dynamically allocated.  To wit:
 X* xp = new X;

The controversy centers around an issue raised here before -- what
happens if the memory gets allocated successfully, but an exception
is thrown in the constructor?

A similar question could be asked about the line
 delete xp;
If the destructor throws an exception, is the memory released or not?

The responses I hear indicate that it is a difficult problem to solve
within the compiler and/or runtime system.  I cannot address this
issue.  But, from the application side, I can illustrate in gory detail
what needs to be done to write correct code without help from the compiler
and runtime system.  To further cloud the issue, my examples will deal
with dynamic allocation and deletion of an array of X objects.

First, a fragment as it would appear if the problem were solve in the
compiler/runtime system:
 X* array = new X[ARRAY_SIZE];
 try {
     do_something(array, ARRAY_SIZE);
 } catch(...) {
     delete array;
     throw;
 }
 delete array;

Now, how to write correct code assuming that the application must
guarantee release of the memory:
 char* mem = new char[ARRAY_SIZE * sizeof(X)];
 int constructed;
 try {
     for (constructed = 0; constructed < ARRAY_SIZE; constructed++)
  new (mem + constructed*sizeof(X)) X;
     do_something(array, ARRAY_SIZE);
 } catch(...) {
     while (--constructed >= 0) {
  X* xp = (X*)(mem + constructed*sizeof(X));
  try {
      xp->~X();
  } catch(...) {
      // ignore
  }
     }
     throw;
 }
 while (--constructed >= 0) {
     X* xp = (X*)(mem + constructed*sizeof(X));
     try {
  xp->~X();
     } catch(...) {
  // Now what to do?
     }
 }
 delete mem;

Several questions become evident:
    1) During dynamic allocation, who deletes the memory if an
 exception is thrown from a contructor?
    2) For an array in which some constructors succeed before one throws
 an exception, who ensures that when the memory is deleted, only
 the fully constructed objects are destroyed?
    3) During destruction of an array of objects, when one of the
 destructors throws an exception, are subequent destructors called?
 Is the memory released?
 Who releases the memory?
 If destruction and deletion proceed in the face of an exception,
 and other exceptions are thrown, which (if any) exception is
 re-thrown?

My recommendations would be:
    1) During dynamic allocation, who deletes the memory if an
 exception is thrown from a contructor?
 *The runtime system.*
    2) For an array in which some constructors succeed before one throws
 an exception, who ensures that when the memory is deleted, only
 the fully constructed objects are destroyed?
 *The runtime system.*
    3) During destruction of an array of objects, when one of the
 destructors throws an exception, are subequent destructors called?
 If the destructors originiated in operator delete, is the memory
 released?
 Who releases the memory?
 If destruction and deletion proceed in the face of an exception,
 and other exceptions are thrown, which (if any) exception is
 re-thrown?
 *These issues seem harder to deal with than those in 1 and 2.
 Because of this, and because failure of destructors seems less
 likely than failure of constructors, it would be acceptible to
 have the standard leave the behavior in these cases undefined.*
--
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




Author: rmartin@rcmcon.com (Robert Martin)
Date: Fri, 15 Oct 1993 14:10:16 GMT
Raw View
Robert Andrew Ryan <rr2b+@andrew.cmu.edu> writes:

>Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
>dtor.. Robert Martin@rcmcon.com (3221)

>> class BuildXFailure
>> {
>>   public:
>>     BuildXFailure(X* theX) : itsX(theX) {}
>>     X* GetX() const {return itsX};
>>     void SetX(X* theX) {itsX = theX;}
>>   private:
>>     X* itsX;
>> };

>> class X
>> {
>>   X() {throw BuildXFailure(this);}
>> };
>> ...
>> try
>> {
>>   X* x = new X;
>> }
>> catch (BuildXFailure xf)
>> {
>>   delete xf.GetX();
>> }


>Uh, this is absolutely ludicrous.

Whew!

>(btw how does the compiler know not to call the destructor on the object?

As you suggest later, the constructor must be reponsible for insuring
that the object is in a destructable state before throwing the
exception (or calling any functions which might...)

>Dynamic allocation of objects is not some bizarre practice of a pagan
>religion and it should not require sacrifices at the altars of pagan
>gods to use it...

Dynamic allocation of memory in C/C++ has always been completely under
the programmer's control.  I think it would be an error to change
that.  IMHO the compiler should not be asked to perform automatic
deletes.

The above code maintains the "programmer controlled" paradigm in that
the programmer must take care of deleting any object which he
allocated with new.

>The
>following would be simple to implement, require far less programmer
>effort, and result in more efficient code:  Require the run-time system
>to free the memory allocated when an object is new'ed and the
>constructor throws an exception.  Require the programmer to be
>responsible for cleaning up a partially constructed object before
>throwing the exception out of the constructor.

This presumes that the throwing of an exception from within a
constructor is an admission that the constructed object should be
deleted.  This is a constraint that should probably not be put on the
language.  The exception may be thrown in situations where the object
should NOT be deleted.

The language should not dictate how its features are used.  Although
it is probably wise to use exceptions for extreme error conditions,
this is not mandated.  By having the compiler deallocate storage if a
contructor throws an exception, you are mandating that exceptions in
constructors only be used in situations where the object must be
destroyed.

>Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
>dtor.. Robert Martin@rcmcon.com (3221)

>> In the case of an array which is an auto variable (i.e. on the stack)
>> then the compiler must unwind the construction by calling the correct
>> destructors IMHO.

>Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
>dtor.. Robert Martin@rcmcon.com (3221)

>> I think that the compiler must keep track of which constructors were
>> called, so that when the array is deleted with delete[], only the
>constructed elements are destructed.

>It sounds like you are suggesting that the semantics of an exception
>thrown from the constructor of an array element should vary depending on
>the method of memory allocation.  This seems wrong to me.

Not at all.  When an exception is thrown out of a scope, the auto
variables within that scope are automatically destroyed.  If one of
those variables happens to be an array, and if the exception was
thrown during the construction of one of its elements, then only those
elements which had been constructed (including the thrower) should be
destructed.

In the case where an array has been allocated with new, and an
exception in thrown in the constructor of one of its elements, the
compiler should IMHO take no action to destroy the array.  If the
programmer chooses to destroy it however, the compiler should only
destruct those elements which had been successfully constructed.  This
is merely an extension of the current rule which says that deleting
arrays destructs all their elements.

--
Robert Martin       | Design Consulting   | Training courses offered:
Object Mentor Assoc.| rmartin@rcmcon.com  |   Object Oriented Analysis
2080 Cranbrook Rd.  | Tel: (708) 918-1004 |   Object Oriented Design
Green Oaks IL 60048 | Fax: (708) 918-1023 |   C++




Author: Robert Andrew Ryan <rr2b+@andrew.cmu.edu>
Date: Fri, 15 Oct 1993 14:58:51 -0400
Raw View
(example code for programmer clean up of allocated memory when a
constructor fails, and argument that compiler shouldn't clean up
allocated memory deleted for brevity)

I find the argument that an exception thrown from a constructor does not
necessarily represent failure compelling, but it does make failing
constructors a real pain to deal with for dynamic allocation.

Excerpts from netnews.comp.lang.c++: 15-Oct-93 Re: exceptions, ctors,
dtor.. Robert Martin@rcmcon.com (4265)

> >(btw how does the compiler know not to call the destructor on the object?

> As you suggest later, the constructor must be reponsible for insuring
> that the object is in a destructable state before throwing the
> exception (or calling any functions which might...)

This gets very complex and error prone when the class might be
subclassed.  If B is derived from A, then if the constructor for A fails
during the construction of a new'ed B how do you avoid the B destructor
doing something nasty due to unitialized data?

The only answer I've come up with so far is to have every base class and
their base classes set a flag to true once their construction has
succeeded and false if it fails.  The derived class must then check each
and every one of these flags and if any of them failed it must assume
the B constructor was never actually called.

-Rob









Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sat, 16 Oct 1993 17:08:49 GMT
Raw View
Robert Andrew Ryan <rr2b+@andrew.cmu.edu> writes:

>It seems a reasonable restriction on the redefinition of new and delete
>that if you override one you must override the other in a manner that
>calling delete on a pointer returned by your new will either free the
>memory or not, but will return normally in any case.  (ie it will not
>throw an exception or abort, or exit your program.)
>
>In the case of placement new the caller has to be responsible for
>deciding what to do if the construction fails.

But what if you are using the so-called "placement" new syntax
as a general purpose allocator, eg.
 Arena my_allocator;
 Object *p = new(my_allocator) Object;
 p->delete_me(my_allocator);

If the compiler were to automatically delete objects allocated with the
standard new operator, but not those allocated with the overloaded new
operator, then the non-standard allocators become very much second
class citizens.

--
Fergus Henderson                     fjh@munta.cs.mu.OZ.AU




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 16 Oct 1993 21:30:12 GMT
Raw View
In article <1993Oct14.144544.3775@rcmcon.com> rmartin@rcmcon.com (Robert Martin) writes:
>>Excerpts from netnews.comp.std.c++: 13-Oct-93 Re: exceptions, ctors,
>>dtor.. Fergus J. HENDERSON@munt (3150)
>
>>> Well, the system must be responsible for ensuring that only and exactly
>>> those fully constructed sub-objects get destroyed.  But as I argued
>>> above I don't think it can possibly be responsible for deallocation,
>>> since you might have redefined delete() to call abort() without it
>>> knowing.
>
>I agree with this sentiment.  The compiler should probably not insert
>calls to delete.  Besides there is a better way:

 Which doesnt work ...
>
>class BuildXFailure
>{
>  public:
>    BuildXFailure(X* theX) : itsX(theX) {}
>    X* GetX() const {return itsX};
>    void SetX(X* theX) {itsX = theX;}
>  private:
>    X* itsX;
>};
>
>class X
>{
>  X() {throw BuildXFailure(this);}
>};
>...
>try
>{
>  X* x = new X;
>}
>catch (BuildXFailure xf)
>{
>  delete xf.GetX();
>}

 No good in general. You are deleting an X* which causes the destructor
of X to be invoked. But the COMPILER has already destroyed
whatever of the object is constructed. Just add a few non-trivial
member variables to class X to see what I mean.

 Throwing an exception during object construction
unwinds the OBJECT as well as the stack. It has to.
Only the compiler is capable of keeping track of which
members and bases have been initialised.

>The above solution has its problems.

 Yeah. It doesnt work. :-)

 At best, you might have to write:

 catch (BuildXFailure xf)
 {
  operator delete(xf.GetX());
 }

But even THAT doesnt work in slightly more general cases.
See below.

>class XConstructorFailure; // some other exception object.
>class X
>{
>  X() {throw XConstructorFailure;} // default constructor used for
>                                   // non dynamic allocation.
>  X(BuildXFailure& xf) {xf.SetX(this); throw xf;}
>                                   // Constructor used for dynamic
>                                   // allocation.
>};
>...
>try
>{
>  BuildXFailure xf;
>  X x;
>  X* xp = new X(xf);
>}
>catch (XConstructorFailure xf)
>{
> .... do whatever.
>}
>catch (BuildXFailure xf)
>{
>  delete xf.GetX();
>}


 Now I want you to consider derived classes and members.
When these are initialised they are initialised by
the use of mem-initialisers. There is no way to trap an exception
thrown from a mem-initialiser.

 What exception is thrown? Well, for your technique
to work, ANY failure must throw a BuildXFailure(X*),
including throws from bases and members.

 Now that means that BuildXfailure must be
initialised:

 1) exactly once with the 'this' of the most derived
    class X
 2) Before any mem-initialisers are called
 3) It must be passed to every member and base
    so it can be thrown if there is a failure

This looks completely impossible.

The *only* way to initialise 'BuildXFailure' with the correct
pointer early enough is for operator new() to do the job.

 class MemoryHolder { void* memory; };
 class Alloc {
  void * operator new(size_t n, MemoryHolder& MH)
  {
   MH.memory = malloc(n);
   return MH.memory;
  }
  ...
 };

 MemoryHolder MH;
 try {
  Alloc * = new(MH) Alloc( ... );
 }
 catch {
  free(MH.memory);
 }


The problem with this technique is that its invasive, and it
isnt secure with MI. That is, you have to derive  all classes
from Alloc to make this work.

>
>Anyway, I think that this situation is manageable without having the
>compiler insert deletes into the code.  And it is consistent with the
>C++ philosophy that the programmer is ultimately responsible for
>managing memory.

 That is fine if, and only if, it can be done at all,
and if, and only if, it can be done reasonably.

 So far, it looks fairly hard to do. No matter
*which* way its done :-(


--
        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: kanze@us-es.sel.de (James Kanze)
Date: 12 Oct 93 15:14:54
Raw View
In article <hopps.750103623@mmm.com> hopps@yellow.mmm.com (Kevin J
Hopps) writes:

|> My recommendations would be:
|>     1) During dynamic allocation, who deletes the memory if an
|>  exception is thrown from a contructor?
|>  *The runtime system.*

If the dynamic allocation is done by means of operator new.  If
placement new is used, or malloc???

|>     2) For an array in which some constructors succeed before one throws
|>  an exception, who ensures that when the memory is deleted, only
|>  the fully constructed objects are destroyed?
|>  *The runtime system.*

This is more or less implied.  This is what happens for objects on the
stack; if the decision is that objects being allocated must be freed,
one would expect the same rules to hold.

|>     3) During destruction of an array of objects, when one of the
|>  destructors throws an exception, are subequent destructors called?
|>  If the destructors originiated in operator delete, is the memory
|>  released?

I believe the tendancy is to say that it is illegal for a destructor
to throw an exception which propagates out, ie: if any exceptions are
thrown during destruction, the destructor must catch them.

|>  Who releases the memory?
|>  If destruction and deletion proceed in the face of an exception,
|>  and other exceptions are thrown, which (if any) exception is
|>  re-thrown?

There are restrictions concerning throwing an exception why handling
another exception.  I'm not too familiar with them, however, and
anyway, I expect they will change somewhat before the standard is
finalized.
--
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: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Wed, 13 Oct 1993 01:28:22 GMT
Raw View
kanze@us-es.sel.de (James Kanze) writes:

>In article <hopps.750103623@mmm.com> hopps@yellow.mmm.com (Kevin J
>Hopps) writes:
>
>|> My recommendations would be:
>|>     1) During dynamic allocation, who deletes the memory if an
>|>  exception is thrown from a contructor?
>|>  *The runtime system.*
>
>If the dynamic allocation is done by means of operator new.  If
>placement new is used, or malloc???

If malloc is used, or the function `operator new()' is called directly,
then construction and allocation are separated anyway, and so it
must be up to the programmer.

If the keyword `new' is used in what I think the ARM calls an
allocation-expression, then the construction and allocation are tied
together.  But the case where you use placement new is a real killer:
the compiler can't call operator delete() to deallocate the memory,
because it might for example be static memory, yet in other cases you
do want operator delete() called.

This is a real can of worms.

In fact I think this shows that the runtime system _can't_  possibly
delete the memory for you, because you might in theory have overloaded
global operator new() to return memory from a static buffer, and global
operator delete() to just call abort().  So long as you don't
use any library functions or classes that might call delete I think this
would be quite legal.  Hence the compiler can't ever insert any calls
to delete() anywhere.

>|>     2) For an array in which some constructors succeed before one throws
>|>  an exception, who ensures that when the memory is deleted, only
>|>  the fully constructed objects are destroyed?
>|>  *The runtime system.*
>
>This is more or less implied.  This is what happens for objects on the
>stack; if the decision is that objects being allocated must be freed,
>one would expect the same rules to hold.

Well, the system must be responsible for ensuring that only and exactly
those fully constructed sub-objects get destroyed.  But as I argued
above I don't think it can possibly be responsible for deallocation,
since you might have redefined delete() to call abort() without it
knowing.

Consider the following code.
 Obj *p = NULL;
 try { p = new Obj; }
 catch (...) {
  /* now what? */
 }
If an exception occurs in the constructor, then p will still be NULL.
The system has already called operator new() and has got a pointer to
some memory.  In general it can't call operator delete() to get rid of
this memory, because operator new() might have been specially
overloaded, or it might be placement operator new(), etc.  It can't
assign this pointer to p, because an exception occurred before that
assignment, and thus assigning the pointer to p would violate the
standard execution sequence.   Assuming that it doesn't catch the
exception, then the only way it could communicate the value of this
pointer back to the catch block `/* now what? */' would be via global
variables.

The conclusion that seems to be almost forced on us is that the only
way to avoid memory leaks when using exceptions and dynamic allocation
in C++ is to use a garbage collector.

--
Fergus Henderson                     fjh@munta.cs.mu.OZ.AU




Author: kanze@us-es.sel.de (James Kanze)
Date: 13 Oct 93 13:15:00
Raw View
In article <9328611.9122@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU
(Fergus James HENDERSON) writes:

|> kanze@us-es.sel.de (James Kanze) writes:

|> >In article <hopps.750103623@mmm.com> hopps@yellow.mmm.com (Kevin J
|> >Hopps) writes:
|> >
|> >|> My recommendations would be:
|> >|>     1) During dynamic allocation, who deletes the memory if an
|> >|>  exception is thrown from a contructor?
|> >|>  *The runtime system.*
|> >
|> >If the dynamic allocation is done by means of operator new.  If
|> >placement new is used, or malloc???

|> If malloc is used, or the function `operator new()' is called directly,
|> then construction and allocation are separated anyway, and so it
|> must be up to the programmer.

|> If the keyword `new' is used in what I think the ARM calls an
|> allocation-expression, then the construction and allocation are tied
|> together.  But the case where you use placement new is a real killer:
|> the compiler can't call operator delete() to deallocate the memory,
|> because it might for example be static memory, yet in other cases you
|> do want operator delete() called.

|> This is a real can of worms.

|> In fact I think this shows that the runtime system _can't_  possibly
|> delete the memory for you, because you might in theory have overloaded
|> global operator new() to return memory from a static buffer, and global
|> operator delete() to just call abort().  So long as you don't
|> use any library functions or classes that might call delete I think this
|> would be quite legal.  Hence the compiler can't ever insert any calls
|> to delete() anywhere.

Unless the standard says it can:-).  Then you have to design your code
to be conform with the standard, ie: to work if delete gets called in
an exception.

I have not been following this issue too closely, so I could be wrong,
but I believe the tendancy is to require the system to free up memory
in the standard case, but you're on your own for placement new.

In practice, I don't think such a decision would break much code.
While replacing global delete with one that calls abort may be
hypothetically legal, I seriously doubt that it is a commonly used
technique.

|> The conclusion that seems to be almost forced on us is that the only
|> way to avoid memory leaks when using exceptions and dynamic allocation
|> in C++ is to use a garbage collector.

I would love to find out that the standards committee *had* to put
garbage collection in to make the things work, IF there were enough
time to do it right, and still get the standard out on time.  But that
is just wishful thinking.
--
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: Robert Andrew Ryan <rr2b+@andrew.cmu.edu>
Date: Wed, 13 Oct 1993 11:05:14 -0400
Raw View
Excerpts from netnews.comp.std.c++: 13-Oct-93 Re: exceptions, ctors,
dtor.. Fergus J. HENDERSON@munt (3150)


> Well, the system must be responsible for ensuring that only and exactly
> those fully constructed sub-objects get destroyed.  But as I argued
> above I don't think it can possibly be responsible for deallocation,
> since you might have redefined delete() to call abort() without it
> knowing.

It seems a reasonable restriction on the redefinition of new and delete
that if you override one you must override the other in a manner that
calling delete on a pointer returned by your new will either free the
memory or not, but will return normally in any case.  (ie it will not
throw an exception or abort, or exit your program.)

In the case of placement new the caller has to be responsible for
deciding what to do if the construction fails.

Without imposing new restrictions on how new and delete may be
implemented,  garbage collection is the only means to avoid core leaks
when constructors for dynamic objects fail.

-Rob
(IMHO and std. disclaimers where appropriate :-)




Author: jln2@cec2.wustl.edu (Sammy D.)
Date: Wed, 13 Oct 1993 17:04:36 GMT
Raw View
In <9328611.9122@mulga.cs.mu.OZ.AU>, fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) scribbles:
: If the keyword `new' is used in what I think the ARM calls an
: allocation-expression, then the construction and allocation are tied
: together.  But the case where you use placement new is a real killer:
: the compiler can't call operator delete() to deallocate the memory,
: because it might for example be static memory, yet in other cases you
: do want operator delete() called.
:
: This is a real can of worms.
:
: In fact I think this shows that the runtime system _can't_  possibly
: delete the memory for you, because you might in theory have overloaded
: global operator new() to return memory from a static buffer, and global
: operator delete() to just call abort().  So long as you don't
: use any library functions or classes that might call delete I think this
: would be quite legal.  Hence the compiler can't ever insert any calls
: to delete() anywhere.


No, I think it shows that before you implement your own version of
new(), that you should think long and hard about how to implement your
own version of delete().  And you had damn sure better provide one.

Really, all that you need to do is to test the pointer passed to
delete() and then invoke the appropriate flavor of free().  For
instance, if global operator new() returns memory from a static buffer,
then global operator delete() had better test for pointers pointing
into that static buffer.

Yes, you can define global operator delete() to just call abort().
Likewise, you can define global operator new() to just call abort().
Don't expect your program to run, however.

--
"For thousands of years, homoeopathic magic was known to the sorcerors of
ancient India, Babylon and Egypt, as well as of Greece and Rome, and at this
day it is still resorted to by cunning and malignant savages in Silicon
Valley, California." -- Anonymous




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Thu, 14 Oct 1993 10:37:26 GMT
Raw View
In article <9328611.9122@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON) writes:

>If the keyword `new' is used in what I think the ARM calls an
>allocation-expression, then the construction and allocation are tied
>together.  But the case where you use placement new is a real killer:
>the compiler can't call operator delete() to deallocate the memory,
>because it might for example be static memory, yet in other cases you
>do want operator delete() called.

 If the standard placement new syntax is used,
no allocation is implied, so no delete will be called
by the run-time system.

>
>This is a real can of worms.
>
>In fact I think this shows that the runtime system _can't_  possibly
>delete the memory for you, because you might in theory have overloaded
>global operator new() to return memory from a static buffer, and global
>operator delete() to just call abort().

 So what? If the global operator new/delete are overriden
by the user, the user must supply them semantics commensurate
with the standard ones.

 In the case you site, a failure of 'new' simply
invokes the delete which calls abort().

>So long as you don't
>use any library functions or classes that might call delete I think this
>would be quite legal.  Hence the compiler can't ever insert any calls
>to delete() anywhere.

 Of course it can. The fact that the system might call
'delete' is a constraint on how you must define 'delete'.
That is, you should expect the system to call delete().

 Overriding the standard new/delete is extremely
dangerous.

>Consider the following code.
> Obj *p = NULL;
> try { p = new Obj; }
> catch (...) {
>  /* now what? */
> }
>If an exception occurs in the constructor, then p will still be NULL.
>The system has already called operator new() and has got a pointer to
>some memory.  In general it can't call operator delete() to get rid of
>this memory, because operator new() might have been specially
>overloaded, or it might be placement operator new(), etc.

 It doesnt have to call 'delete()' *in general*.
Only if the global (default) new was used. (by default
I dont mean the system supplied 'new', I mean the
normal 'new' other than placement new or overloaded variants)

>The conclusion that seems to be almost forced on us is that the only
>way to avoid memory leaks when using exceptions and dynamic allocation
>in C++ is to use a garbage collector.

 :-)

Provided its stated therein, I'll report the status of this
in the latest WP due in my post box in a few days.


--
        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: rmartin@rcmcon.com (Robert Martin)
Date: Thu, 14 Oct 1993 14:45:44 GMT
Raw View
>Excerpts from netnews.comp.std.c++: 13-Oct-93 Re: exceptions, ctors,
>dtor.. Fergus J. HENDERSON@munt (3150)

>> Well, the system must be responsible for ensuring that only and exactly
>> those fully constructed sub-objects get destroyed.  But as I argued
>> above I don't think it can possibly be responsible for deallocation,
>> since you might have redefined delete() to call abort() without it
>> knowing.

I agree with this sentiment.  The compiler should probably not insert
calls to delete.  Besides there is a better way:

class BuildXFailure
{
  public:
    BuildXFailure(X* theX) : itsX(theX) {}
    X* GetX() const {return itsX};
    void SetX(X* theX) {itsX = theX;}
  private:
    X* itsX;
};

class X
{
  X() {throw BuildXFailure(this);}
};
...
try
{
  X* x = new X;
}
catch (BuildXFailure xf)
{
  delete xf.GetX();
}

I don't think it is inappropriate for users who are going to create
dynamic objects to have to deal with their exceptional deallocation by
themselves.  Thus it should be incumbent upon the programmer, when
throwing an exception from a constructor, to include the address of
the object being constructed within the exception.  This allows the
function doing the allocation to decide whether or not it should be
deallocated.

The above solution has its problems.  For example:

try
{
  X x; // what if this fails ?
  X* xp = new X;
}
catch (BuildXFailure xf)
{
  delete xf.GetX(); // this will be nasty if the first constructor fails.
}


Possibly a better scheme would be something of this nature:

class XConstructorFailure; // some other exception object.

class X
{
  X() {throw XConstructorFailure;} // default constructor used for
                                   // non dynamic allocation.
  X(BuildXFailure& xf) {xf.SetX(this); throw xf;}
                                   // Constructor used for dynamic
                                   // allocation.
};
...
try
{
  BuildXFailure xf;
  X x;
  X* xp = new X(xf);
}
catch (XConstructorFailure xf)
{
 .... do whatever.
}
catch (BuildXFailure xf)
{
  delete xf.GetX();
}

Anyway, I think that this situation is manageable without having the
compiler insert deletes into the code.  And it is consistent with the
C++ philosophy that the programmer is ultimately responsible for
managing memory.

------------------------------

On the issue of arrays.  If some of the elements of an array get
constructed and then one of the constructors throws an exception, how
do the constructed elements get destructed?  In the case of an array
which is an auto variable (i.e. on the stack) then the compiler must
unwind the construction by calling the correct destructors IMHO.
Also, when arrays are allocated with new, I think that the compiler
must keep track of which constructors were called, so that when the
array is deleted with delete[], only the constructed elements are
destructed.  This does not seem to be too great a burden to place upon
the compiler....



--
Robert Martin       | Design Consulting   | Training courses offered:
Object Mentor Assoc.| rmartin@rcmcon.com  |   Object Oriented Analysis
2080 Cranbrook Rd.  | Tel: (708) 918-1004 |   Object Oriented Design
Green Oaks IL 60048 | Fax: (708) 918-1023 |   C++




Author: Robert Andrew Ryan <rr2b+@andrew.cmu.edu>
Date: Thu, 14 Oct 1993 14:34:24 -0400
Raw View
Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
dtor.. Robert Martin@rcmcon.com (3221)

> Besides there is a better way:

> class BuildXFailure
> {
>   public:
>     BuildXFailure(X* theX) : itsX(theX) {}
>     X* GetX() const {return itsX};
>     void SetX(X* theX) {itsX = theX;}
>   private:
>     X* itsX;
> };

> class X
> {
>   X() {throw BuildXFailure(this);}
> };
> ...
> try
> {
>   X* x = new X;
> }
> catch (BuildXFailure xf)
> {
>   delete xf.GetX();
> }


Uh, this is absolutely ludicrous.  (btw how does the compiler know not
to call the destructor on the object? though I suppose you could require
the programmer to catch the exception in  the constructor and set things
up so that the destructor may be called...)  Dynamic allocation of
objects is not some bizarre practice of a pagan religion and it should
not require sacrifices at the altars of pagan gods to use it...  The
following would be simple to implement, require far less programmer
effort, and result in more efficient code:  Require the run-time system
to free the memory allocated when an object is new'ed and the
constructor throws an exception.  Require the programmer to be
responsible for cleaning up a partially constructed object before
throwing the exception out of the constructor.

Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
dtor.. Robert Martin@rcmcon.com (3221)

> In the case of an array which is an auto variable (i.e. on the stack)
> then the compiler must unwind the construction by calling the correct
> destructors IMHO.

Excerpts from netnews.comp.std.c++: 14-Oct-93 Re: exceptions, ctors,
dtor.. Robert Martin@rcmcon.com (3221)

> I think that the compiler must keep track of which constructors were
> called, so that when the array is deleted with delete[], only the
constructed elements are destructed.

It sounds like you are suggesting that the semantics of an exception
thrown from the constructor of an array element should vary depending on
the method of memory allocation.  This seems wrong to me.

-Rob










Author: Kostya Vasilyev <Kostya@Symantec.com>
Date: 14 Oct 1993 16:07:17 GMT
Raw View
In article <1993Oct13.170436.23152@wuecl.wustl.edu> Sammy D.,
jln2@cec2.wustl.edu writes:
>Really, all that you need to do is to test the pointer passed to
>delete() and then invoke the appropriate flavor of free().  For
>instance, if global operator new() returns memory from a static buffer,
>then global operator delete() had better test for pointers pointing
>into that static buffer.

I think it is possible to implement the compiler such that even this is
not necessary.  When you new() an object, the compiler knows (1) which
class is being new'd so it knows what delete() to use and (2) it can only
call delete if the standard-looking new [i.e. new(size_t)] was called.

--------------------------------------------------------------------------
Kostya Vasilyev, SYMANTEC Corporation, Bedrock group
Cytomax Junkie  10201 Torre Avenue
   Cupertino, CA 95014
   (408) 446-7165
   eMail: Kostya_Vasilyev_at_SYMCU-DEV@Symantec.com
--------------------------------------------------------------------------




Author: Jim Lloyd <jlloyd@symantec.com>
Date: 14 Oct 1993 19:48:04 GMT
Raw View
In article <CEvuuF.8Gv@ucc.su.OZ.AU> John Max Skaller,
maxtal@physics.su.OZ.AU writes:
> It doesnt have to call 'delete()' *in general*.
>Only if the global (default) new was used. (by default
>I dont mean the system supplied 'new', I mean the
>normal 'new' other than placement new or overloaded variants)

I hope that if a class defines operator new(size_t) and
operator delete, then the compiler would call the class-scoped
operator delete.

A couple months back I posted a request asking if it made
sense to consider that if a class doesn't define operator
new(size_t) then it "inherits" the global operator
new(size_t).  I was quickly convinced that this cannot be the
case, which is too bad.  Consider the following psuedo code:

template <class T>
T* New(/* constructor arguments*/)
{
  T* p = T::operator new(sizeof(T));
  try
  {
    new(p) T(/* constructor arguments*/);
  }
  catch(...)
  {
    T::operator delete(p);
    throw;
  }
}

This template function New behaves the way I hope a standard
new expression will work when C++ is finally standardized.
Note that this psuedo code assumes that the compiler does not
call delete when an exception is thrown from a constructor
using placed new.  I also think that the compiler should not
call delete for any other overloaded variant of operator new.

While working on a simple persistence mechanism, I had
occasion to try to write the above code, where the
constructor arguments where fixed so I could write real code
instead of pseudo code. (I needed to separate allocation from
construction for a different reason than exception handling.)
However, I was foiled in my attempt because the template
function fails to compile for any class T that does not
provide a definition of operator new and delete (which of
course is most classes).

Since classes do not "inherit" the global new and delete, it
will not be possible to write code like the above, but even
so, the compiler/runtime should provide equivalent behavior,
or the convenience/usability of C++ exception handling is
seriously diminished (IMO).

Jim Lloyd
jlloyd@symantec.com