Topic: Asymmetry: "new (placement) T" vs. "Tptr->~T


Author: jmd@dlogics.COM (Jens M. Dill)
Date: 29 Aug 90 20:37:44 GMT
Raw View
Is anyone else bothered by the inherent asymmetry in the application of
constructors and destructors to an object that is to reside in a known
memory location?  To take a known area of memory and intitialize it as an
object of type T, one is supposed to use the "placement" form of the "new"
operator, which implicitly invokes any necessary constructor:

            T* Tptr = new (KnownArea) T ;

To undo this, one is supposed to apply the destructor ~T explicitly:

            Tptr->~T () ;

This is all very well if one knows whether or not T is a class with a
destructor, but consider the case where T is a type argument to a template
(or to a macro that emulates the template facility).  In this case, one
cannot know if T is a type with constructors or destructors, since that
information is unavailable until T is bound in a template-class definition.

This causes no problems with the constructor invocation, since the constructor
is invoked implicitly by operator new, which can be applied to objects of any
type, whether or not they have constructors.  The problem comes with ensuring
that the destructor is properly invoked.  Here, one must invoke the destructor
explicitly, since operator delete may not be applied to memory not allocated
by operator new.  But one may not invoke the destructor explictly if no
destructor id defined.  And one has no way of telling, for a template type-
argument T, whether or not T has a destructor.

Possible fixes:

1.  Define a version of the delete operator that accepts an indicator telling
    it that the memory is not to be returned to the heap, as it was allocated
    by some other mechanism.  This is the most symmetric and general solution,
    but I can think of no good syntax for it, and it perpetuates a problem
    introduced by the "new" placement syntax:  namely, that "new" and "delete"
    are being invoked for their side effects only (constructor/destructor
    invocation) rather than for their intended purpose (heap allocation/
    deallocation).

2.  Make it legal to explicitly invoke a destructor for any type T, whether
    or not it has one.  Invocations of the form "Tptr->~T" where T is a
    primitive type or the name of a class without a destructor would do
    nothing.  This solves the problem, even though it retains the asymmetry.

3.  Other variants, etc.  I do not see an obious best choice here.


For those who are interested, the problem arose when defining a stack
template that allocates space in multi-element frames.  Rather than
initializing all elements of the stack frame at the time the frame was
allocated (which would be relatively easy), I wanted to initialize each
element as it receieved a new value (via PUSH) and invoke the destructor
for the corresponding POP.  There is a workaround:  declare yet another
container class that holds just one stack element and defines only the
new and delete operators to have the PUSH and POP semantics.  Perhaps
that workaround is purer C++, even though it introduces a gratuitous
class definition.

*=====* TIME CANNOT BE WASTED *=====*       -- Jens M. Dill
 \ But it can be used for purposes /           jmd@dlogics.com
  \ other than what was intended. /
   *=============================*




Author: ark@alice.UUCP (Andrew Koenig)
Date: 30 Aug 90 19:23:27 GMT
Raw View
In article <620@dlogics.COM>, jmd@dlogics.COM (Jens M. Dill) writes:

> Is anyone else bothered by the inherent asymmetry in the application of
> constructors and destructors to an object that is to reside in a known
> memory location?

The asymmetry is an intrinsic part of the operation.  In one case
you're taking raw memory and building an object.  In the other,
you're destroying an object that already exists.

> To undo this, one is supposed to apply the destructor ~T explicitly:

>             Tptr->~T () ;

> This is all very well if one knows whether or not T is a class with a
> destructor, but consider the case where T is a type argument to a template
> (or to a macro that emulates the template facility).  In this case, one
> cannot know if T is a type with constructors or destructors, since that
> information is unavailable until T is bound in a template-class definition.

Yes, that is indeed a problem.  The original article suggested several
solutions, and one of those has actually been written into the manual.

See Ellis & Stroustrup, page 280:

 The notation for explicit call of a destructor
 may be used for any simple type name.  For example:

  int* p;
  // ...
  p->int::~int();

 Using the notation for a type that does not have a
 destructor has no effect.  Allowing this enables people
 to write code without having to know if a destructor
 exists for a given type.

This isn't implemented everywhere yet, but I hope it will be.
--
    --Andrew Koenig
      ark@europa.att.com




Author: jmd@dlogics.COM (Jens M. Dill)
Date: 31 Aug 90 16:40:06 GMT
Raw View
In article <620@dlogics.COM>, I wrote:
> Is anyone else bothered by the inherent asymmetry in the application of
> constructors and destructors to an object that is to reside in a known
> memory location?  ...

  ... many more lines ...

> Possible fixes:
>
> 1.  (deleted)

> 2.  Make it legal to explicitly invoke a destructor for any type T, whether
>     or not it has one.  Invocations of the form "Tptr->~T" where T is a
>     primitive type or the name of a class without a destructor would do
>     nothing.  This solves the problem, even though it retains the asymmetry.

> 3.  (deleted)

  ...

I recieved the following answer by e-mail from Jeremy Grodberg:
: I am unable to post to the net, you may post this or incorporate it in
: something you post, if you like.
:
: According to E&S 12.4 (page 280):
:
:  Using the notation [for explicit call of a destructor] for a type that
:  does not have a destructor has no effect.  Allowing this enables people
:  to write code without having to know if a destructor exists for a
:  given type.
:
: So I think what you need is a conforming compiler (which AT&T CFront 2.0
: does not seem to be), rather than a change to the standards.
: --
:
: Jeremy Grodberg
: jgro@apldbio.com    "Beware: free advice is often overpriced!"

It turns out that I had my copy of E&S sitting ON MY DESK, open to that VERY
PAGE.  Thanks, Jeremy, for calling my attention to it.  Sometimes, free advice
is worth more than its price would have you believe.

But what is this about CFront 2.0 being non-conforming?  2.0 predates E&S,
so I'd expect certain things in E&S to be of the implemented-in-2.1-but-not-
in-2.0 variety.  Are you trying to tell me this is one of them?  If so,
thanks again.  My only criticism of E&S so far is that it does not make it
clear which releases of Cfront support which features (I know that Cfront
is not supposed to be anything but one among many compilers for C++, but
it IS the de facto industry standard, and things like "compatible with
Cfront x.xx" have meaning in the real world: a meaning that is sometimes
rard to correlate with E&S).

*=====* TIME CANNOT BE WASTED *=====*       -- Jens M. Dill
 \ But it can be used for purposes /           jmd@dlogics.com
  \ other than what was intended. /
   *=============================*




Author: jimad@microsoft.UUCP (Jim ADCOCK)
Date: 4 Sep 90 18:38:34 GMT
Raw View
In article <620@dlogics.COM| jmd@dlogics.COM (Jens M. Dill) writes:
|Is anyone else bothered by the inherent asymmetry in the application of
|constructors and destructors to an object that is to reside in a known
|memory location?  To take a known area of memory and intitialize it as an
|object of type T, one is supposed to use the "placement" form of the "new"
|operator, which implicitly invokes any necessary constructor:
|
|            T* Tptr = new (KnownArea) T ;
|
|To undo this, one is supposed to apply the destructor ~T explicitly:
|
|            Tptr->~T () ;

The asymmetry exists, should the language attempt to hide it?  New creates
an object where one did not previously exist.  How can you use object
syntax on something that isn't [yet] an object?  Conversely, shouldn't
destruction syntax reflect that destruction is being applied to something that
*is* an object?

|This is all very well if one knows whether or not T is a class with a
|destructor, but consider the case where T is a type argument to a template
|(or to a macro that emulates the template facility).  In this case, one
|cannot know if T is a type with constructors or destructors, since that
|information is unavailable until T is bound in a template-class definition.
|
|This causes no problems with the constructor invocation, since the constructor
|is invoked implicitly by operator new, which can be applied to objects of any
|type, whether or not they have constructors.  The problem comes with ensuring
|that the destructor is properly invoked.  Here, one must invoke the destructor
|explicitly, since operator delete may not be applied to memory not allocated
|by operator new.  But one may not invoke the destructor explictly if no
|destructor id defined.  And one has no way of telling, for a template type-
|argument T, whether or not T has a destructor.
|
|Possible fixes:
|
|1.  Define a version of the delete operator that accepts an indicator telling
|    it that the memory is not to be returned to the heap, as it was allocated
|    by some other mechanism.  This is the most symmetric and general solution,
|    but I can think of no good syntax for it, and it perpetuates a problem
|    introduced by the "new" placement syntax:  namely, that "new" and "delete"
|    are being invoked for their side effects only (constructor/destructor
|    invocation) rather than for their intended purpose (heap allocation/
|    deallocation).

I disagree that the intended purpose of new and delete is to allocate/
deallocate memory.  I claim instead the the intended purpose of new and
delete is the dynamic creation / destruction of objects.  "Placement"
syntax then makes perfect sense -- the parameters allow one to specify
variations in where one dynamically creates an object.  The fact that heap
allocations and deallocations may need to be performed to allow objects
to be dynamically created and destroyed is the side effect, not the primary
effect of new and delete..

|2.  Make it legal to explicitly invoke a destructor for any type T, whether
|    or not it has one.  Invocations of the form "Tptr->~T" where T is a
|    primitive type or the name of a class without a destructor would do
|    nothing.  This solves the problem, even though it retains the asymmetry.

It is already legal to explicitly call [do-nothing] destructors on built-in
types [E&S pg 280]  Other classes should explicitly declare destructors,
if so needed.  If a given protocol, such as might be required by a stack
template, requires a destructor, then put a destructor in the protocol
definition.  Derived classes then automatically get a destructor.
Conversely, a class writer may explicitly choose to not have a destructor.
Such a design decision might be reasonable if writing a class whose objects
are intended never to be destroyed.  Allowing a compiler to generate
destructors for classes from the void prevents such a design choice.

I counter-propose: no change.