Topic: Exceptions and dynamic allocation
Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Sun, 20 Feb 1994 19:14:47 GMT Raw View
In article <1994Feb8.121447.24986@cdf.toronto.edu> g2devi@cdf.toronto.edu (Robert N. Deviasse) writes:
>
>I personally don't mind the delete[] notation. I just wish C++ provided
>some way of distinguishing between array pointers and pointers to single
>objects.
Well, you ought to be a happy man then...
typedef int *ip;
typedef int (*iap)[10];
The above declarations declare two very different typedef names.
(The *real* problem is that expressions like `new T[10]' are defined to
yield (T*) rather than (T(*)[10]). But this particular language botch
is just too well entrenched to be fixed at this late date.)
--
-- Ron Guilmette, Sunnyvale, CA ---------- RG Consulting -------------------
---- domain addr: rfg@netcom.com ----------- Purveyors of Compiler Test ----
---- uucp addr: ...!uunet!netcom!rfg ------- Suites and Bullet-Proof Shoes -
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 20 Feb 1994 22:45:56 GMT Raw View
>rmartin@rcmcon.com (Robert Martin) writes:
>
>>Yes, conjugal new/delete pairs would be nice.
Actually, provided you design a class based
delete with this in mind, it can probably be done.
The 'new(parms)' can 'register' the deallocator,
and 'delete' can call it. Have to store the
id of the deallocator (probably a pointer) in the
memory new allocates, and maybe other information
such as the 'arena' in which the allocation occurred.
--
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 Henderson)
Date: Tue, 8 Feb 1994 02:59:02 GMT Raw View
g2devi@cdf.toronto.edu (Robert N. Deviasse) writes:
>rmartin@rcmcon.com (Robert Martin) writes:
>>
>>Anyway, it is a complex issue. I would prefer seeing a comprehensive
>>policy for new/delete conjugal pairs. But I would not like to see the
>>delete[] nightmare extended.
>
>The problem in this is that you're asking each and every "new" to store
>extra information (i.e. at least, does it have any parameters) so that
>they may be appended. This adds extra overhead to "new" calls that do
>not use the feature.
>
>Allocating arrays already adds this overhead, so why not extend the meaning
>of delete[]? I mean, we would write:
> A alpha;
> X* xp = new(alpha) X;
> // ...
> delete[] xp;
>It's a bit irksome, but I believe that it could be added without adding
>extra overhead to array calls (instead of storing the size of the array,
>store "-1" to indicate that extra parameters and an alternate "delete"
>need to be called. I'd prefer a different syntax, for this delete, but
>I'm just mentioning this one here as a possibility.
Using the `delete []' syntax for this new meaning would be very
confusing, IMHO.
>I don't think any code will be broken by the proposal since code using
>new(alpha) X without a corresponding delete(alpha) would still have
>the same behaviour.
No, it wouldn't, since `new(alpha) X' would still have to store the
extra information even if it wasn't used. In particular your proposal
would break the use of new(void *) as a way to explicitly invoke a
constructor, eg.
// Allocate memory for array using malloc() so that we can
// later resize it using realloc().
// We need to explicitly call the constructors ourselves
// using placement operator new.
Foo *array = (Foo *) malloc(n * sizeof(Foo));
for (int i = 0; i < n; i++) {
new(&array[i]) Foo;
}
This would break because `new(&array[i]) Foo' would have to store the
additional info, but we haven't allocated any space for the additional
info.
--
Fergus Henderson - fjh@munta.cs.mu.OZ.AU
Author: g2devi@cdf.toronto.edu (Robert N. Deviasse)
Date: Tue, 8 Feb 1994 12:14:47 GMT Raw View
In article <9403913.19939@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
>g2devi@cdf.toronto.edu (Robert N. Deviasse) writes:
>
>>rmartin@rcmcon.com (Robert Martin) writes:
>>>
>>>Anyway, it is a complex issue. I would prefer seeing a comprehensive
>>>policy for new/delete conjugal pairs. But I would not like to see the
>>>delete[] nightmare extended.
>>
>>The problem in this is that you're asking each and every "new" to store
>>extra information (i.e. at least, does it have any parameters) so that
>>they may be appended. This adds extra overhead to "new" calls that do
>>not use the feature.
>>
>>Allocating arrays already adds this overhead, so why not extend the meaning
>>of delete[]? I mean, we would write:
>> A alpha;
>> X* xp = new(alpha) X;
>> // ...
>> delete[] xp;
>>It's a bit irksome, but I believe that it could be added without adding
>>extra overhead to array calls (instead of storing the size of the array,
>>store "-1" to indicate that extra parameters and an alternate "delete"
>>need to be called. I'd prefer a different syntax, for this delete, but
>>I'm just mentioning this one here as a possibility.
>
>Using the `delete []' syntax for this new meaning would be very
>confusing, IMHO.
>
Agreed, I suggested it as a "place holder syntax" until a better one
can be though up. Let's say for the sake of argument we use the:
Foo* foo=new(alpha) Foo;
delete(...) foo;
syntax. It's at least partially intuitive and shouldn't cause any parsing
problems/ambiguities.
This raises a question. Now that we can overload "operator new[]", can we
overload the placement "operator new[]"? If so we would also have to add the
syntax:
Foo* foo=new(alpha) Foo[100];
delete[](...) foo;
>>I don't think any code will be broken by the proposal since code using
>>new(alpha) X without a corresponding delete(alpha) would still have
>>the same behaviour.
>
>No, it wouldn't, since `new(alpha) X' would still have to store the
>extra information even if it wasn't used. In particular your proposal
>would break the use of new(void *) as a way to explicitly invoke a
>constructor, eg.
>
> // Allocate memory for array using malloc() so that we can
> // later resize it using realloc().
> // We need to explicitly call the constructors ourselves
> // using placement operator new.
>
> Foo *array = (Foo *) malloc(n * sizeof(Foo));
> for (int i = 0; i < n; i++) {
> new(&array[i]) Foo;
> }
>
>This would break because `new(&array[i]) Foo' would have to store the
>additional info, but we haven't allocated any space for the additional
>information.
Why would you need to store information for this case? The information
stored for "ptr=new(alpha) Foo" is only used only by "delete(...) ptr"
(originally delete[] ptr). It would be undefined to call "delete(...) ptr"
if no placement delete were defined.
Remember, originally C++ had the deletion syntax:
Foo* foo=new Foo[100];
delete[100] foo;
It was error-prone and inconvenient to use this notation. It was eventually
changed to
Foo* foo=new Foo[100];
delete[] foo;
I don't know if the following code was originally meant to produce undefined
results (I don't have my 1st edition of "The C++ programming language" any
more):
Foo *array = (Foo *) malloc(n * sizeof(Foo));
for (int i = 0; i < n; i++) {
new(&array[i]) Foo;
}
delete[100] array;
But I'm sure that people assumed that it should and compilers did also since
no additional information should be needed.
I personally don't mind the delete[] notation. I just wish C++ provided
some way of distinguishing between array pointers and pointers to single
objects.
I personally don't mind the delete(...) notation, but I think that repeating
the "delete[100]" semantics would be a big mistake.
>
>--
>Fergus Henderson - fjh@munta.cs.mu.OZ.AU
Take care
Robert
--
/----------------------------------+------------------------------------------\
| 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: g2devi@cdf.toronto.edu (Robert N. Deviasse)
Date: Tue, 8 Feb 1994 12:25:14 GMT Raw View
In article <9403913.19939@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
>g2devi@cdf.toronto.edu (Robert N. Deviasse) writes:
>
>>rmartin@rcmcon.com (Robert Martin) writes:
>>>
>>>Anyway, it is a complex issue. I would prefer seeing a comprehensive
>>>policy for new/delete conjugal pairs. But I would not like to see the
>>>delete[] nightmare extended.
>>
>>The problem in this is that you're asking each and every "new" to store
>>extra information (i.e. at least, does it have any parameters) so that
>>they may be appended. This adds extra overhead to "new" calls that do
>>not use the feature.
>>
>>Allocating arrays already adds this overhead, so why not extend the meaning
>>of delete[]? I mean, we would write:
>> A alpha;
>> X* xp = new(alpha) X;
>> // ...
>> delete[] xp;
>>It's a bit irksome, but I believe that it could be added without adding
>>extra overhead to array calls (instead of storing the size of the array,
>>store "-1" to indicate that extra parameters and an alternate "delete"
>>need to be called. I'd prefer a different syntax, for this delete, but
>>I'm just mentioning this one here as a possibility.
>
>Using the `delete []' syntax for this new meaning would be very
>confusing, IMHO.
>
Agreed, I suggested it as a "place holder syntax" until a better one
can be though up. Let's say for the sake of argument we use the:
Foo* foo=new(alpha) Foo;
delete(...) foo;
syntax. It's at least partially intuitive and shouldn't cause any parsing
problems/ambiguities.
This raises a question. Now that we can overload "operator new[]", can we
overload the placement "operator new[]"? If so we would also have to add the
syntax:
Foo* foo=new(alpha) Foo[100];
delete[](...) foo;
>>I don't think any code will be broken by the proposal since code using
>>new(alpha) X without a corresponding delete(alpha) would still have
>>the same behaviour.
>
>No, it wouldn't, since `new(alpha) X' would still have to store the
>extra information even if it wasn't used. In particular your proposal
>would break the use of new(void *) as a way to explicitly invoke a
>constructor, eg.
>
> // Allocate memory for array using malloc() so that we can
> // later resize it using realloc().
> // We need to explicitly call the constructors ourselves
> // using placement operator new.
>
> Foo *array = (Foo *) malloc(n * sizeof(Foo));
> for (int i = 0; i < n; i++) {
> new(&array[i]) Foo;
> }
>
>This would break because `new(&array[i]) Foo' would have to store the
>additional info, but we haven't allocated any space for the additional
>information.
Why would you need to store information for this case? The information
stored for "ptr=new(alpha) Foo" is only used only by "delete(...) ptr"
(originally delete[] ptr). It would be undefined to call "delete(...) ptr"
if no placement delete were defined.
Remember, originally C++ had the deletion syntax:
Foo* foo=new Foo[100];
delete[100] foo;
It was error-prone and inconvenient to use this notation. It was eventually
changed to
Foo* foo=new Foo[100];
delete[] foo;
I don't know if the following code was originally meant to produce undefined
results (I don't have my 1st edition of "The C++ programming language" any
more):
Foo *array = (Foo *) malloc(n * sizeof(Foo));
for (int i = 0; i < n; i++) {
new(&array[i]) Foo;
}
delete[100] array;
But I'm sure that people assumed that it should and compilers did also since
no additional information should be needed.
I personally don't mind the delete[] notation. I just wish C++ provided
some way of distinguishing between array pointers and pointers to single
objects.
I personally don't mind the delete(...) notation, but I think that repeating
the "delete[100]" semantics would be a big mistake.
If you wanted to do
Foo *array = (Foo *) malloc(n * sizeof(Foo));
for (int i = 0; i < n; i++) {
new(&array[i]) Foo;
}
what's wrong with defining?
Foo *array;
new(array,malloc) Foo[n];
It seems clearer and will do the right thing when you call:
delete[](...) array;
if you define operator delete(void*,void*(*)(void*)) appropriately.
That way, independent of allocation strategy, the array will be deleted
properly.
Comments?
>
>--
>Fergus Henderson - fjh@munta.cs.mu.OZ.AU
Take care
Robert
--
/----------------------------------+------------------------------------------\
| 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: sfc@datascope.com (Steve F. Cipolli (P/M))
Date: Fri, 28 Jan 1994 20:48:15 GMT Raw View
Stan Friesen (swf@tdat.ElSegundoCA.NCR.COM) wrote:
: Now note, if this is a derived class, the constructor for the *base*
: class portion *has* been complteted at the time the body of the constructor
: is executed, so the base class destructor *is* called.
: Also, any sub-objects will have had *thier* constructors completed, so
: the destructors of any sub-objects are called as well.
I would like someone to confirm that (fully constructed) sub-objects are
destructed during stack unwinding in a constructor. I have read other
postings which seem to indicate that sub-objects are not destructed. I'm
only asking to get everyone reading this thread on the same page.
--Stephen Cipolli
Author: sfc@datascope.com (Steve F. Cipolli (P/M))
Date: Fri, 28 Jan 1994 20:40:10 GMT Raw View
Fergus Henderson (fjh@munta.cs.mu.OZ.AU) wrote:
: sfc@datascope.com (Steve F. Cipolli (P/M)) writes:
: >If a throw occurs during a constructor of a new'ed object, is the object's
: >memory freed during the throw/stack-unwinding process.
: Yes, it should be, IMHO.
: The ARM is however not clear on this, and I don't think the
: committee has addressed the issue.
I received an e-mail message from a person at Sun Microsystems which brings
up several barriers to the implementation of this. Here is the relevant
parts of the message:
----->
The problem is that there is no way for *either* the implementation or the
user to reliably free the memory.
Here briefly is why :
allocation and initialization are separate operations
at the point of initialization (i.e. in the constructor) there is
no record of how the memory has been allocated (e.g. by a user-defined
new, by a placement new, by a class specific new), so how can
the compiler "free" the memory after an exception in the constructor ?
Even if there were such a record there may not *be* any "inverse"
allocation operation e.g. a class with an operator new, but no operator
delete (you know you'll never deallaocate any of them).
So the compiler can't do it - but neither can the user:
X::X() {
...
throw 911; // destructors for members and bases (at least)
// executed when stack is unwound
}
X* p;
try {
p = new X;
}
catch( ... ) { delete p; } // will run dtor's a second time
<-----
: >For example:
: >
: >class A {
: > char *x;
: > char *y;
: >public:
: > A() {
: > A::x = new char[10];
: > throw(int(1));
: > A::y = new char[20];
: > }
: Here A::x will be allocated but never deallocated, so you will get
: a memory leak.
Are you sure, other postings seem to indicate that any sub-object of the
object being constructed are destructed during stack unwinding? I'm only
asking to confirm the concepts and get myself and everyone reading this
thread on the same page.
: There is no definitive answer to be found in the ARM, unfortunately.
: But it seems clear to me that deallocating the memory allocated by
: a new expression _must_ be the compiler's responsibility, because
: there is basically no way for the programmer to do it.
I agree with your assessment, I hope the C++ comittee does as well and soon.
There is another thread started by Kevin Hopps which asks the same question
as I've posed, so the issue must be becoming important to users.
--Stephen Cipolli
Author: bobkf@news.delphi.com (BOBKF@DELPHI.COM)
Date: 30 Jan 1994 07:21:35 -0500 Raw View
Stephen Cipolli writes:
>I would like someone to confirm that (fully constructed) sub-objects are
>destructed during stack unwinding in a constructor. I have read other
>postings which seem to indicate that sub-objects are not destructed. I'm
>only asking to get everyone reading this thread on the same page.
You mean the page where para 2 of section 15.3 appears?
Bob Foster
objfactory@aol.com
Author: barmar@think.com (Barry Margolin)
Date: 31 Jan 1994 17:17:54 GMT Raw View
In article <1994Jan28.204010.10211@datascope.com> sfc@datascope.com (Steve F. Cipolli (P/M)) writes:
>The problem is that there is no way for *either* the implementation or the
>user to reliably free the memory. >Here briefly is why : > > allocation
and initialization are separate operations
True, but they are both invoked by the same implementation code.
> at the point of initialization (i.e. in the constructor) there is
> no record of how the memory has been allocated (e.g. by a
user-defined > new, by a placement new, by a class specific new), so how
can > the compiler "free" the memory after an exception in the
constructor ?
It doesn't have to be done in the constructor, it can be done in the code
that implements the "new" operator. This code can know what type of
allocation is being done, and can perform the appropriate inverse, if
possible. It can set up its own catch around the call to the constructor.
The code generated by the new operator might be like the following
pseudo-code:
temp_ptr = allocator<class>();
try { for subobjects of *temp_ptr {
subobject.constructor();
remember that this subobject has been initialized;
}
}
catch (...) {
for initialized subobjects of *temp_ptr {
subobject.destructor();
}
temp_ptr->deallocate();
throw;
}
allocator() and deallocate() would be calls to the appropriate operator
new() and operator delete() member functions (this is not the same as uses
of those operators from user code, since the operators run the
contstructors and destructors, but the above code is at a lower level).
> Even if there were such a record there may not *be* any "inverse"
> allocation operation e.g. a class with an operator new, but no operator
> delete (you know you'll never deallaocate any of them).
C++ gives the programmer plenty of rope in this regard. If they define a
new operator but not a delete operator, then memory leaks are a possible
result. Perhaps defining a non-placement new operator but not a delete
operator should be ruled illegal, since the
>So the compiler can't do it - but neither can the user:
>
> X::X() {
> ...
> throw 911; // destructors for members and bases (at least)
> // executed when stack is unwound
> }
>
> X* p;
> try {
> p = new X;
> }
> catch( ... ) { delete p; } // will run dtor's a second time
Will it? If the constructor throws a condition p should never be
assigned, so the delete will be given an uninitialized pointer (presumably
in real code you would initialize p to NULL, so the delete would have no
effect).
>
><-----
>
>: >For example:
>: >
>: >class A {
>: > char *x;
>: > char *y;
>: >public:
>: > A() {
>: > A::x = new char[10];
>: > throw(int(1));
>: > A::y = new char[20];
>: > }
>
>: Here A::x will be allocated but never deallocated, so you will get
>: a memory leak.
>
>Are you sure, other postings seem to indicate that any sub-object of the
>object being constructed are destructed during stack unwinding? I'm only
>asking to confirm the concepts and get myself and everyone reading this
>thread on the same page.
>
>: There is no definitive answer to be found in the ARM, unfortunately.
>: But it seems clear to me that deallocating the memory allocated by
>: a new expression _must_ be the compiler's responsibility, because
>: there is basically no way for the programmer to do it.
>
>I agree with your assessment, I hope the C++ comittee does as well and soon.
>There is another thread started by Kevin Hopps which asks the same question
>as I've posed, so the issue must be becoming important to users.
>
>--Stephen Cipolli
>
--
Barry Margolin
System Manager, Thinking Machines Corp.
barmar@think.com {uunet,harvard}!think!barmar
Author: rmartin@rcmcon.com (Robert Martin)
Date: Mon, 31 Jan 1994 20:31:01 GMT Raw View
For clarity, I would like to summarize some of the issues regarding
exceptions thrown from within constructors. Where appropriate I will
express the mechamism that I think should be employed. I would
appreciate correction from anyone within the standards committee.
When an exception is thrown within a constructor, all successful
constructions involving that object should be undone by calling the
appropriate destructors. Thus, destructors should be called for:
1. All constructed members.
2. All base classes.
3. All previously constructed array elements (if it is an
array that is being constructed.)
The destructor coresponding to the constructor that failed, should not
be called. If there are cleanup issues they should be handled within
the constructor itself as follows:
X::X() throw (horrible_problem)
{
itsPointer = 0;
try
{
itsPointer = new X; // allocate something.
...
throw(horrible_problem); // or call function that throws...
}
catch (...)
{
delete itsPointer; // cleanup
throw; // pass the exception along.
}
}
----------------------------------------------------------------
If the exception was thrown out of a constructor invoked by operator
new, then there is a dilemma. In most cases operator delete should be
automatically called. However it is possible to overload operator new
with an algorithm which does not expect delete to be called. The most
notorious of these is "placement new":
void* operator new (size_t, void* v) {return v;}
This operator is used to construct objects in place, or to reinvoke
constructors, etc (See Coplien: "Advanced C++ Programming Styles and
Idioms").
If this kind of operator new is used to allocate an object, and if the
subsequent constructor throws an exception, then delete should *not*
be called.
Currently, there is no way for the compiler to detect if delete should
or should not be called. However, this can be corrected by a change
to the syntax for declaring operator new.
void* operator new(size_t [, args]) static;
The use of the static keyword in this position could signify to the
compiler that delete is not to be used with this operator new. Thus
the compiler would not invoke delete if an exception passes out of an
invocation of an operator new declared in this fashion.
Comments?
--
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: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Wed, 2 Feb 1994 15:26:25 GMT Raw View
rmartin@rcmcon.com (Robert Martin) writes:
>If the exception was thrown out of a constructor invoked by operator
>new, then there is a dilemma. In most cases operator delete should be
>automatically called. However it is possible to overload operator new
>with an algorithm which does not expect delete to be called. The most
>notorious of these is "placement new":
[...]
>If this kind of operator new is used to allocate an object, and if the
>subsequent constructor throws an exception, then delete should *not*
>be called.
>
>Currently, there is no way for the compiler to detect if delete should
>or should not be called. However, this can be corrected by a change
>to the syntax for declaring operator new.
>
> void* operator new(size_t [, args]) static;
>
>The use of the static keyword in this position could signify to the
>compiler that delete is not to be used with this operator new. Thus
>the compiler would not invoke delete if an exception passes out of an
>invocation of an operator new declared in this fashion.
>
>Comments?
Please, let's not add yet another meaning for `static'! ;-)
Your proposed extension would allow the programmer to tell the compiler
whether or not delete should be called, but would not let the
programmer tell the compiler _which_ delete should be called. In
particular, your solution wouldn't help if their are multiple
overloaded new operators which each require a different action for
deallocation.
Here's my alternative suggestion: allow overloaded operator
delete(), as discussed in the ARM (5.3.4, page 66), and
require that the compiler call the operator delete() with exactly
the same extra arguments that were passed to the operator new().
This would be easy to implement, and easy for programmers to
understand.
--
Fergus Henderson - fjh@munta.cs.mu.OZ.AU
Author: mark@sidell.win.net (Mark Sidell)
Date: Tue, 01 Feb 1994 21:28:10 GMT Raw View
In article <1994Jan28.204010.10211@datascope.com>, Steve F. Cipolli (P/M) (sfc@datascope.com) writes:
...
>Are you sure, other postings seem to indicate that any sub-object of the
>object being constructed are destructed during stack unwinding? I'm only
>asking to confirm the concepts and get myself and everyone reading this
>thread on the same page.
>
>: There is no definitive answer to be found in the ARM, unfortunately.
>: But it seems clear to me that deallocating the memory allocated by
>: a new expression _must_ be the compiler's responsibility, because
>: there is basically no way for the programmer to do it.
>
>I agree with your assessment, I hope the C++ comittee does as well and soon.
>There is another thread started by Kevin Hopps which asks the same question
>as I've posed, so the issue must be becoming important to users.
>
>--Stephen Cipolli
>
>
Stephen, very interersting discussion! You may be interested to
know that BC++ 4.0 has the following behaviour when a ctor for an
object constructed on the heap fails: It neither calls the dtors
for fully-constructed base classes or members, nor frees the memory.
I, for one, can't see how you can write general-purpose classes
whose constructors do interesting things like allocate memory (and
which therefore may throw exceptions) without having the compiler
clean things up when they are created on the heap and fail. It
seems like you have two poor choices: Don't let ctors fail, or
don't don't let users construct your objects on the heap.
Does anyone else have a general method for dealing with this
problem? Pete Becker, how are you folks at Borland doing it?
-Mark Sidell, Forte, Inc.
Author: g2devi@cdf.toronto.edu (Robert N. Deviasse)
Date: Fri, 4 Feb 1994 17:56:04 GMT Raw View
In article <1994Feb3.173149.1217@rcmcon.com> rmartin@rcmcon.com (Robert Martin) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
>
>>rmartin@rcmcon.com (Robert Martin) writes:
>
>>>Currently, there is no way for the compiler to detect if delete should
>>>or should not be called. However, this can be corrected by a change
>>>to the syntax for declaring operator new.
>>>
>>> void* operator new(size_t [, args]) static;
>>>
>>>The use of the static keyword in this position could signify to the
>>>compiler that delete is not to be used with this operator new. Thus
>>>the compiler would not invoke delete if an exception passes out of an
>>>invocation of an operator new declared in this fashion.
>>>
>>>Comments?
>
>>Please, let's not add yet another meaning for `static'! ;-)
>
> <<I had thought about =0 instead...>>
>
>>Your proposed extension would allow the programmer to tell the compiler
>>whether or not delete should be called, but would not let the
>>programmer tell the compiler _which_ delete should be called. In
>>particular, your solution wouldn't help if their are multiple
>>overloaded new operators which each require a different action for
>>deallocation.
>
>>Here's my alternative suggestion: allow overloaded operator
>>delete(), as discussed in the ARM (5.3.4, page 66), and
>>require that the compiler call the operator delete() with exactly
>>the same extra arguments that were passed to the operator new().
>
>>This would be easy to implement, and easy for programmers to
>>understand.
>
>Yes, conjugal new/delete pairs would be nice. And they solve the
>"exception in a constructor invoked by new" problem. However there
>are some issues:
>
> Must the new/delete conjugal pairs be monogamous? i.e.
> X* xp = new(A) X;
> delete(A,B) xp;
> Is this undefined? If so, some existing code will break.
> I think that there are two options:
>
> 1. The syntax for invoking delete is 'delete x'. The
> compiler is responsible for selecting the correct
> overloaded delete operator and supplying the same
> arguments used for new.
>
> i.e. void* operator new(size_t, A);
> void operator delete(size_t, A);
>
> A alpha; X* xp = new(alpha) X;
> delete xp; // calls delete(size_t, A)
> // with the value of alpha in
> // the second argument.
>
> 2. Caveat emptor. The user can call any delete he
> wants, regardless of which (if any) new was used.
> This extends the delete [] nightmare without bound.
>
> If option 2 above is chosen then:
>
> Given: A alpha; X* xp = new(alpha) X;
>
> If X::X throws an exception, the compiler knows that it
> should invoke delete(A), but what value for A does it use.
> The only reasonable choice is 'alpha', but this may not be
> what the user intended....
But if that were so then
A alpha; X* xp = new(alpha) X;
would fail to call the right delete if the construction of X failed.
So like it or not, alpha would have to be called. Unless I'm missing
something.
>
>Anyway, it is a complex issue. I would prefer seeing a comprehensive
>policy for new/delete conjugal pairs. But I would not like to see the
>delete[] nightmare extended.
>
The problem in this is that you're asking each and every "new" to store
extra information (i.e. at least, does it have any parameters) so that
they may be appended. This adds extra overhead to "new" calls that do
not use the feature.
Allocating arrays already adds this overhead, so why not extend the meaning
of delete[]? I mean, we would write:
A alpha;
X* xp = new(alpha) X;
// ...
delete[] xp;
It's a bit irksome, but I believe that it could be added without adding
extra overhead to array calls (instead of storing the size of the array,
store "-1" to indicate that extra parameters and an alternate "delete"
need to be called. I'd prefer a different syntax, for this delete, but
I'm just mentioning this one here as a possibility.
I don't think any code will be broken by the proposal since code using
new(alpha) X without a corresponding delete(alpha) would still have
the same behaviour. This also solves your concern since if:
X* x=new(alpha) X;
delete(beta) x;
were wanted, then we would write it using the current idiom of directly
calling the destructor and then deallocating the memory to beta.
Thoughts?
>
>
>--
>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++
Take care,
Robert
--
/----------------------------------+------------------------------------------\
| 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: sfc@datascope.com (Steve F. Cipolli (P/M))
Date: Wed, 26 Jan 1994 22:26:46 GMT Raw View
Steve F. Cipolli (P/M) (sfc@datascope.com) wrote:
: If a throw occurs during a constructor of a new'ed object, is the object's
: memory freed during the throw/stack-unwinding process.
: For example:
: class A {
: char *x;
: char *y;
: public:
: A() {
: A::x = new char[10];
: throw(int(1));
: A::y = new char[20];
: }
: ~A() {
: delete A::x;
: delete A::y;
: }
: };
: void Func() {
: A *a;
: try {
: a = new A; // Assuming standard C++ runtime
: }
: catch(int) {
: // Has (a) been freed at this point?
: // If not, then will the following correctly destruct/free
: // the partially constructed object (only A::x should be
: // destructed)?
: delete a;
: }
: }
After further thought I would like to retract the second part of my question.
It is obvious from the original proposal by Koenig and Stroustrup that the
half-constructed object is a bozo-no-no.
Also, to clarify the main point of my question: Since the throw occurred
within the constructor, the assignment of the address of the dynamic memory
allocated for the object of class A is never assigned to (a). If the code
generated does not delete the memory, then how can that memory be reclaimed?
The only way I can see out of this, is to catch all exceptions in the
constructor, make the object semi-sane (presumably a zombie) and NOT rethrow.
This then would allow the assignment of (a) to happen. The problem here is
the code surrounding the new/constructor doesn't know the object is a
zombie. Thus, the object (a) is never deleted and is likely to be used by
subsequent code.
I must be missing something here, please enlighten me.
--Stephen
Author: sfc@datascope.com (Steve F. Cipolli (P/M))
Date: Mon, 24 Jan 1994 20:40:49 GMT Raw View
If a throw occurs during a constructor of a new'ed object, is the object's
memory freed during the throw/stack-unwinding process.
For example:
class A {
char *x;
char *y;
public:
A() {
A::x = new char[10];
throw(int(1));
A::y = new char[20];
}
~A() {
delete A::x;
delete A::y;
}
};
void Func() {
A *a;
try {
a = new A; // Assuming standard C++ runtime
}
catch(int) {
// Has (a) been freed at this point?
// If not, then will the following correctly destruct/free
// the partially constructed object (only A::x should be
// destructed)?
delete a;
}
}
I am currently awaiting a version of C++ which provides exception handling and
this topic has particular relevance for evolving our software into C++'s
exception handling. Also, I have been unable to find any definitive
documentation on this particular topic (neither the ARM nor the original
proposal as published in "The Evolution of C++" speaks to this issue).
I have seen postings which suggest that (a) is freed during the throw, but
I would like a definitive answer from someone close to the topic. Please
forgive me if this topic has been covered previously.
--Stephen
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Wed, 26 Jan 1994 00:24:01 GMT Raw View
sfc@datascope.com (Steve F. Cipolli (P/M)) writes:
>If a throw occurs during a constructor of a new'ed object, is the object's
>memory freed during the throw/stack-unwinding process.
Yes, it should be, IMHO.
The ARM is however not clear on this, and I don't think the
committee has addressed the issue.
>For example:
>
>class A {
> char *x;
> char *y;
>public:
> A() {
> A::x = new char[10];
> throw(int(1));
> A::y = new char[20];
> }
Here A::x will be allocated but never deallocated, so you will get
a memory leak.
> ~A() {
> delete A::x;
> delete A::y;
> }
>};
>
>void Func() {
> A *a;
> try {
> a = new A; // Assuming standard C++ runtime
> }
> catch(int) {
> // Has (a) been freed at this point?
No. The memory which was allocated by `new A' has been freed,
but `a' is still an uninitialized variable, since the exception
was thrown before the value could be assigned to `a'.
> // If not, then will the following correctly destruct/free
> // the partially constructed object (only A::x should be
> // destructed)?
> delete a;
No, this will attempt to delete an uninitialized pointer and will
hence result in undefined behaviour.
> }
>}
>
>I have seen postings which suggest that (a) is freed during the throw, but
>I would like a definitive answer from someone close to the topic. Please
>forgive me if this topic has been covered previously.
There is no definitive answer to be found in the ARM, unfortunately.
But it seems clear to me that deallocating the memory allocated by
a new expression _must_ be the compiler's responsibility, because
there is basically no way for the programmer to do it.
When it sees an expression
X *p = new X;
it should generate code something like
X *tmp = (X *)operator new(sizeof(X));
try {
tmp->X::X(); // call the constructor
}
catch(...) {
operator delete(tmp, sizeof(X));
throw;
}
X * p = tmp;
The bad news with this is that it may call operator delete() without
any explicit mention of it in the source code. Presumeably the
compiler should still report an access error if operator delete() is
not accessible. And things are a little more complicated when it comes
to overloaded new operators - I guess that if you call an overloaded
operator new(), the compiler should call the corresponding overloaded
operator delete().
The committee needs to look at this issue if they haven't done so
already.
--
Fergus Henderson - fjh@munta.cs.mu.OZ.AU
Author: matt@physics16.berkeley.edu (Matt Austern)
Date: 26 Jan 1994 06:17:41 GMT Raw View
In article <9402611.28338@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
> >If a throw occurs during a constructor of a new'ed object, is the object's
> >memory freed during the throw/stack-unwinding process.
>
> Yes, it should be, IMHO.
> The ARM is however not clear on this, and I don't think the
> committee has addressed the issue.
The real question I have about throwing exceptions during a
constructor isn't whether the object's memory gets freed (the answer
clearly ought to be yes, even if this doesn't appear explicitly
in the ARM), but whether the object's destructor gets called.
I couldn't find anything explicit about this in the ARM, but my
assumption is that, as with any other object, the destructor is called
as part of the stack-unwinding if the object is allocated as an
automatic variable, but it is not called if the object is created
using new. Have I missed anything in the ARM that would clear
this up?
This question matters very much, of course, if you're writing a
constructor that might throw an exception. The question is: what
state should an object be in when its constructor throws an exception?
Should it be in some sane state that the destructor can deal with, or
should it already have done whatever cleanup the destructor does, or
both?
--
Matthew Austern Never express yourself more clearly
matt@physics.berkeley.edu than you think. ---N. Bohr
Author: eedste@teamos43.ericsson.se (Stefan Eissing)
Date: 26 Jan 1994 11:04:47 GMT Raw View
In article <MATT.94Jan25221741@physics16.berkeley.edu> matt@physics16.berkeley.edu (Matt Austern) writes:
>This question matters very much, of course, if you're writing a
>constructor that might throw an exception. The question is: what
>state should an object be in when its constructor throws an exception?
>Should it be in some sane state that the destructor can deal with, or
>should it already have done whatever cleanup the destructor does, or
>both?
I think the object should always be in a state where the destructor can
safely be called. Maybe even before its constructor is called, you never know.
At least first thing to do is setting all pointers to 0, then you may call
new for them, but don't forget to check for the return value. Someone
may have turned the new_handler off.
Btw. If I use pre-exception-time libraries, I got to turn the new_handler
off, right? For which libraries do I have to turn it on again? If I'm
writing a library, is it better to deal with both possibilities?
--
___________________________________________________________________
Stefan Eissing Ericsson Eurolab
eedste@aachen.ericsson.se OSS Design & Market Support
Author: swf@tdat.ElSegundoCA.NCR.COM (Stan Friesen)
Date: Wed, 26 Jan 94 08:10:24 PST Raw View
In article <MATT.94Jan25221741@physics16.berkeley.edu>, matt@physics.berkeley.edu writes:
|>
|> The real question I have about throwing exceptions during a
|> constructor isn't whether the object's memory gets freed (the answer
|> clearly ought to be yes, even if this doesn't appear explicitly
|> in the ARM), but whether the object's destructor gets called.
This is not a question unless th committe has reopened it. The
current answer is *no* it does not get called. Only *completely*
constructed objects have their destructor called. The object in
the constructor of which an exception is thrown has not been
completely constructed.
Now note, if this is a derived class, the constructor for the *base*
class portion *has* been complteted at the time the body of the constructor
is executed, so the base class destructor *is* called.
Also, any sub-objects will have had *thier* constructors completed, so
the destructors of any sub-objects are called as well.
|> This question matters very much, of course, if you're writing a
|> constructor that might throw an exception. The question is: what
|> state should an object be in when its constructor throws an exception?
Any state at all - the constructor is unfinished so the object
does not "exist" yet as far as C++ is concerned.
|> Should it be in some sane state that the destructor can deal with, or
|> should it already have done whatever cleanup the destructor does, or
|> both?
If you have allocated any resources by some means other than initializing
a sub-object, you should free them before throwing the exception.
[This is another advantage using of the "resource allocation is
initialization" strategy].
--
swf@elsegundoca.ncr.com sarima@netcom.com
The peace of God be with you.