Topic: exceptions/ctors/dtors/heap


Author: hopps@yellow.mmm.com (Kevin J Hopps)
Date: Mon, 11 Oct 93 13:23:34 GMT
Raw View
Apologies if this is a duplicate posting.  My original try failed somehow...

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: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Tue, 12 Oct 1993 10:18:09 GMT
Raw View
hopps@yellow.mmm.com (Kevin J Hopps) writes:

>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.

Just to illustrate it in even more gory detail, I'll point out
one or two minor errors in your code - this stuff really is tricky
to write!

>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)];

Is there any guarantee that memory allocated with new char[x] will be
suitably aligned?  I think perhaps you should use
 char *mem = (char *)operator new(ARRAY_SIZE * sizeof(X));
instead.

> 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

Note that this behaviour is different to the behaviour that would occur
if the problem were handled by the compiler; in the latter case, if
an exception occurs while in the middle of handling an exception, then
some special function (terminate(), I think) will be called.

>  }
>     }
>     throw;
> }
> while (--constructed >= 0) {
>     X* xp = (X*)(mem + constructed*sizeof(X));
>     try {
>  xp->~X();
>     } catch(...) {
>  // Now what to do?
>     }
> }
> delete mem;

This last line should be
 delete[] mem;
or
 operator delete(mem);
to match with the way mem was allocated.

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