[Prev][Next][Index]

Re: alternative to "trashed"



Gary --

> Nice to hear from you.

It's nice to hear form you, too!

> I agree that the constructor can't assume the invariant.  This argues
> that perhaps some special syntax should be used so that users
> don't get confused.

I'm not sure I understand; I thought that C++ constructors do have a
fairly special syntax.

> Yes.  I agree that operator delete is a separete concept from ~T(),
> in theory.  Looking through Ellis and Stroustrup, it seems as if
> what you suggest is in fact illegal, however.  Is that right?
> If not, can the same thing be done with operator new and a
> constructor?

I was surprised to find this out, as well.  But it seems to be true.
Check out page 279 of the ARM.  Also, the following program works under
both g++ deccxx:

--- test.cxx ------------------------------------------------------------
#include <stream.h>

class T {
  int i;
public:
  T() { i = 0; }
  ~T() { cout << "In T::~T().\n"; }
};

int main() {
  T* t = new T;
  t->~T();
  t->~T();
  delete t;
}
----------------------------------------------------------------------
The output is

[/kt22]::~/c++/test> test
In T::~T().
In T::~T().
In T::~T().
[/kt22]::~/c++/test>

There is a potential complicated difficulty here, as destructors are
called on objects with virtual functions and base classes, the vptr
must "swing" to that of the base class before executing the base
class' destructor.  Is the destructor required to restore it to its
original value, to make the second invocation work correctly?  The ARM
is fairly silent on this point.  This question is relevant to
something we're working on, too; I may query comp.std.c++ and see if
any real language lawyers care to respond.

>>   Symmetrically, destructors are distinguished member functions that get
>>   to assume the class invariant on entry, but aren't required to
>>   maintain them on exit.  Thus, if a class has a non-trivial invariant,
>>   it is necessarily illegal to call a member function on an object after
>>   its destructor is called; you can't establish the pre-condition that
>>   the invariant holds!

> But it's even illegal to call a member function if the invariant is trivial
> (:-).

I'm not sure I get your meaning; are you saying that it's illegal to
call a member function on an object after its destructor has been
called, even if the object has a trivial invariant the the destructor
didn't invalidate?  You could just define things this way; I was
proposing a rationale for what distinguishes the destructor from other
member functions.  If we accept that calling a destructor without
doing a delete, via "p->~T()",  is legal, then there is especially
good reason to treat the destructor differently from any other member
function.  Usually, it will happen to be different from other member
functions in that it doesn't preserve the class invariant, in which
case no member function can be called on an object after the
destructor has.  But this is not always necessary, as in the case
we're discussing.

> the effect of
>	new Stack();
> and	delete my_stackp;
> which is what the client can use.
> I think this makes sense, as the constructor can't be called again on an
> object.  On the other hand, we don't talk about the result of new and the
> argument of delete.
> The only thing that worries me still is that one can construct an
> object (on the run-time stack) without using new,
> and destroy an object implictly by leaving a block.
> 	{ Stack x;
>	  ...
>	}

This seems to me exactly the reason for *not* mixing up the
specification of the storage state with the effects of constructors
and destructors; they really are complete separate in C++.  If I write
a class T, there is *no way* I can keep a client from allocating
instances of T on both the stack in the heap.  The semantic meaning of
"new T" *really is* the composition of a call to "::operator new"
followed by a constructor, and "delete p" really is a destructor
invocation followed by "::operator delete".  So I persist in thinking
that the specification of "operator new" and "operator delete" is
where you should deal with FRESH and TRASHED.

Think of it this way: C++ really isn't that different from C.  In C,
you would call "malloc", and then, by convention, call an initialization
routine to establish an invariant.  Similarly, you might call a
destruction routine followed by "free".  C++ just codifies these
conventions.

Here, by the way, is how I think the semantics of malloc and free
should be handled (straight out of "malloc plumber" implementations.")

Every concrete pointer value should have an abstract value that is a
3-tuple: <loc, inHeap, objectId>.  loc is the address, in-heap is a
boolean that is true if the pointer is into the heap, and, if in-heap
is TRUE, object-id is a unique id of the malloc call that allocated
the storage block the pointer points into.

"malloc" modifies an abstract variable "validMap", which maps
addresses into object id's.  There is a unique object id
"unallocated", which is the id of unallocated storage.  "validMap"
initially maps all addresses into "unallocated."  The spec of malloc
is then (excuse the syntax)

  void* malloc(size_t size);

  ensures
    maxObjId' = maxObjId` + 1
    ForAll i in { RETVAL.loc, ..., RETVAL.loc + size }:
       validMap\pre[i] = unallocated
    ForAll i in { RETVAL.loc\pre, ..., RETVAL\pre + size }:
       validMap\post[i] = maxObjId'
    RETVAL.in-heap = TRUE
    RETVAL.objectId = maxObjId

An abstract pointer value <loc, inHeap, objectId> with inHeap TRUE is
valid iff validMap[loc] = objectId.

For what it's worth...

Dave


    


Follow-Up(s):