Topic: vptrs after destructors...


Author: adk@Warren.MENTORG.COM (Ajay Kamdar)
Date: 22 Mar 1993 14:46:02 -0500
Raw View
In article <1993Mar19.210815.17191@borland.com> eomiya@borland.com (Elliot Omiya) writes:
>
>One safe thing to do is as I wrote in the first article:
>
>ap->~A;
>ap->A = NULL;
>
>A second (inadvertent?) call to ap->~A will do nothing.
>
>Unfortunately, the *syntax* of the second call to the destructor is correct
>so the compiler cannot know that it is illegitimate.
>

Given,
    A *ap = 0;

What is the basis for the statement that
    ap->A::~A();

is safe and that the DTOR call will do nothing? Where in the ARM
is a statement guaranteeing this to be safe? I don't see how this
guarantee can be necessarily deduced from the guarantee that "delete ap"
will work even if ap is zero.


- Ajay

--
I speak for none but myself.

Ajay Kamdar                               Email : ajay_kamdar@mentorg.com
Mentor Graphics, IC Group (Warren, NJ)    Phone : (908) 604-0842




Author: eomiya@borland.com (Elliot Omiya)
Date: Tue, 23 Mar 1993 00:44:27 GMT
Raw View
ARM Section 5.3.4, page 63 ("Delete")

"Deleting a pointer with the value zero ... is guaranteed to be harmless."

Note that the ARM also states that destructing an object does not render an
lvalue zero:  To wit:

"...if the expression denoting the object in a delete expression is a
modifiable lvalue, its value is undefined after the deletion."

Which means that a pointer to an object which is used in a delete call is
**not** set to NULL after the delete, this is the programmer's responsibility.
A subsequent call of delete on the (now)-NULLed pointer will be harmless.


--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------




Author: adk@Warren.MENTORG.COM (Ajay Kamdar)
Date: 23 Mar 1993 11:38:28 -0500
Raw View
In article <1993Mar23.004427.7184@borland.com> eomiya@borland.com (Elliot Omiya) writes:

  - Explanation of ARM rules on deleting a pointer with value zero removed -

>Which means that a pointer to an object which is used in a delete call is
>**not** set to NULL after the delete, this is the programmer's responsibility.
>A subsequent call of delete on the (now)-NULLed pointer will be harmless.
>

If you refer back to your previous postings, you will notice that you
made a statement to the effect that explicitly calling a class' destructor
via a zero pointer is harmless.

All your ARM quotes refer to deletion via a zero pointer. You still haven't
explained how ARM's guarantee that deleting a zero pointer is harmless
translates to a guarantee that explicitly calling a class' destructor via
a zero pointer is harmless.

If a C++ implementation shares the same function to execute the destructor
and to free the memory, then the chances are pretty good that explicitly
calling a destructor via a zero pointer will be harmless. For example, cfront
generates code in which a delete statement and a call to the destructor
via a pointer are both translated into a function call to the same function
with different arguments which control whether the memory is released or not.
(The ARM mentions that this is a popular technique used by compiler
writers.) This function has a check for a zero pointer before it does
any thing.  Given cfront's implementation, explicitly calling a class'
destructor via a zero pointer will indeed be harmless. However, in the
absence of any explicit language requiring an explicit call to a destructor
via a zero pointer to be harmless, this is a totally implementation
dependent behavior.

A C++ implementation might generate C code such that:

    delete ap;

gets translated to:
 if (ap) {
  __call_dtor(ap);     // execute the destructor
        // Check for ap being zero is not made in
        // __call_dtor().
  __free_mem(ap);      // releases the memory
 }

and

 ap->~A();

gets translated to:
 __call_dtor(ap);         // execute the destructor
        // if ap is zero, and that causes a problem,
        // too bad!

If __call_dtor() does not special case on getting a zero pointer as
its argument (since the ARM does not require it to), then explicitly
calling a class destructor via a zero pointer will result in undefined
behavior.

If you dereference a zero pointer to call any other member function,
you would expect undefined behavior. Why would you expect anything
else if you dereference a zero pointer to call the destructor?

- Ajay

--
I speak for none but myself.

Ajay Kamdar                               Email : ajay_kamdar@mentorg.com
Mentor Graphics, IC Group (Warren, NJ)    Phone : (908) 604-0842




Author: eomiya@borland.com (Elliot Omiya)
Date: Tue, 23 Mar 1993 23:37:51 GMT
Raw View
>All your ARM quotes refer to deletion via a zero pointer. You still haven't
>explained how ARM's guarantee that deleting a zero pointer is harmless
>translates to a guarantee that explicitly calling a class' destructor via
>a zero pointer is harmless.

Argh.  I quote the ARM and this is the end of my postings on this subject:

Section 5.3.4, "Delete" (page 63 in my edition):

"Deleting a pointer with the value zero, however, is guaranteed to be
harmless."

End of (my) discussion.
--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------




Author: adk@Warren.MENTORG.COM (Ajay Kamdar)
Date: 24 Mar 1993 11:29:49 -0500
Raw View
In article <1993Mar23.233751.10111@borland.com> eomiya@borland.com (Elliot Omiya) writes:
>
>>All your ARM quotes refer to deletion via a zero pointer. You still haven't
>>explained how ARM's guarantee that deleting a zero pointer is harmless
>>translates to a guarantee that explicitly calling a class' destructor via
>>a zero pointer is harmless.
>
>Argh.  I quote the ARM and this is the end of my postings on this subject:
>
>Section 5.3.4, "Delete" (page 63 in my edition):
>
>"Deleting a pointer with the value zero, however, is guaranteed to be
>harmless."
>
>End of (my) discussion.


Sigh. I presumed I was having a serious discussion with someone
who understood the difference between deleting an object and explicitly
calling a destructor, and had some reason for writing what he did.
Seemingly, my premise was incorrect; I apologize for not realizing that
sooner.

I will end my contribution to this thread by including a fragment of
the post where a surprising claim was made:


>In article <1993Mar19.210815.17191@borland.com> eomiya@borland.com (Elliot Omiya) writes:
>
>One safe thing to do is as I wrote in the first article:
>
>ap->~A;
>ap->A = NULL;
>
>A second (inadvertent?) call to ap->~A will do nothing.

Sorry for the wasted bandwidth on a discussion where the original poster
did not care to substantiate his claims.

- Ajay

--
I speak for no one but myself.

Ajay Kamdar                               Email : ajay_kamdar@mentorg.com
Mentor Graphics, IC Group (Warren, NJ)    Phone : (908) 604-0842




Author: jimad@microsoft.com (Jim Adcock)
Date: 24 Mar 93 18:50:20 GMT
Raw View
In article <1onee4$vu@ajay.Warren.MENTORG.COM> adk@Warren.MENTORG.COM (Ajay Kamdar) writes:
|If a C++ implementation shares the same function to execute the destructor
|and to free the memory, then the chances are pretty good that explicitly
|calling a destructor via a zero pointer will be harmless. For example, cfront
|generates code in which a delete statement and a call to the destructor
|via a pointer are both translated into a function call to the same function
|with different arguments which control whether the memory is released or not.
|(The ARM mentions that this is a popular technique used by compiler
|writers.) This function has a check for a zero pointer before it does
|any thing.  Given cfront's implementation, explicitly calling a class'
|destructor via a zero pointer will indeed be harmless.

Really?  What code does cfront generate in this situation if the
destructor is declared virtual?  Won't you still possibly get an
attempted virtual dispatch through a null pointer?





Author: philr@dspcproj.demon.co.uk (Phil Reynolds)
Date: 25 Mar 93 10:59:34 GMT
Raw View
In article <1993Mar23.233751.10111@borland.com> eomiya@borland.com (Elliot Omiya) writes:

> Argh.  I quote the ARM and this is the end of my postings on this subject:
>
> Section 5.3.4, "Delete" (page 63 in my edition):
>
> "Deleting a pointer with the value zero, however, is guaranteed to be
> harmless."
>
> End of (my) discussion.
> --
> -----------------------------------------------------------------------------
> Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
> -----------------------------------------------------------------------------
>

Argh...! This still begs the question: Are

  delete pA;

and

  pA->~A();

the same thing, or does one delete the memory and the other just
call the destructor and leave the memory intact?

Unfortunatly I only have an old implementation of CFront to test
this with and that doesn't allow the second version of the
syntax.
What happens if class A has a virtual destructor?

  class A
  {
  public:
    // ...
    virtual ~A();
    // ...
  };

When doing a 'delete pA' the version of CFront that I am using
currently checks for a NULL pointer, then gets the vtable
pointer and calls the relevent destructor which deletes the
memory. If it is leagal to call pA->~A() for a NULL pointer then
the compiler must special case this call (as opposed to any
other virtual function call) as getting the pointer to the
vtable from a NULL object pointer will cause all sorts of memory
access violation problems.

+-------------------------------------+
|Phil Reynolds                        |
|PC Projects                          |
|Datastream International Ltd.        |
|philr@dspcproj.demon.co.uk (I think!)|
+-------------------------------------+




Author: eomiya@borland.com (Elliot Omiya)
Date: Fri, 26 Mar 1993 16:08:35 GMT
Raw View
ABSTRACT:  1.  Apology  2.  Long discussion on delete vs. destructor functions.

>Sigh. I presumed I was having a serious discussion with someone
>who understood the difference between deleting an object and explicitly
>calling a destructor, and had some reason for writing what he did.
>Seemingly, my premise was incorrect; I apologize for not realizing that
>sooner.

I will apologize for being somewhat obnoxious, however, some people have not
exactly been polite in their communications (and no I am not naming names or
blaming anyone in specific).  If I have offended anyone or appeared boorish
again, I apologize.

On the subject of calling a destructor twice versus calling the destructor
function twice, here is what I know (how about, my opinion) on this:

Basically, once a destructor has been called and the memory for the object
has been freed, any pointers to that object which are now stale are very
dangerous.  Calling a method on a destroyed object may succeed (because the
memory was freed but not altered), but of course, this behavior is unintended
and is never guaranteed to "work."

In the case of "pA->~A", I will clarify by saying that if the call
"delete pA" is made, followed by an immediate pA = NULL, a subsequent call
to pA->~A will fail in some predictable way because most compilers and
operating environments do something predictable when a zero pointer is
dereferenced (Borland C for Windows GP's).  This is in line with what
the gentlemen from Microsoft mentioned about virtuals.  This does not
address the question of calling pA->~A twice, but see the discussion
below.

With respect to calling pA->~A at all, this call seems to assume that someone
else will free the memory for the object.  If the "owner" of pA (the outer
scope function or object) actually called the new operator then I question
whether pA->~A should be called in favor of just calling "delete pA."
However, it makes sense to call the destructor function explicitly (as
opposed to through the delete operator) if at the current scope, the
"owner" of pA has been passed this pointer rather than having acquired
it through the new operator.  In this case, a call to pA->~A can serve
the purpose of emasculating the object while leaving the memory cleanup
to someone else.  This must be a carefully planned strategy, however,
because when the delete operator is called the destructor function will
then be called twice.

Whew!  Now, as for calling a destructor function twice (in contrast to
destructing an object twice), this may be absolutely OK, or absolutely
disastrous.  For example, consider the case of the following class:

class myClass
{
public:
    myClass(const char *filename) { myFile = fopen("filename", "w+"); }
    ~myClass(void) { fclose(myFile); }

private:
    FILE *myFile;
};

Calling this destructor twice (through pA->~A) would be pretty disastrous
(at least on an IBM PC under DOS) because closing a stream I/O file twice
corrupts memory.

Consider this class:

class myClass
{
public:
    myClass(FILE *aFile) : aFile(NULL) { myFile = aFile }
    ~myClass(void) { aFile = NULL; }

private:
    FILE *myFile;
};

In this case, calling the destructor function twice merely sets aFile to NULL
twice which is absolutely benign.  (Presumably this function merely does
something with the file pointer and is not responsible for opening and closing
it).

I *believe* that the language specification says nothing about calling
destructor functions twice because the "function" that is the destructor
is really no different than any other function except that it has the special
attribute that is called specifically by the "delete" operator.  Other than
that, you can call it any number of times.  I posted a short fragment of
code on this thread earlier which illustrates this.

Summary:
1.  Don't call delete twice.
2.  If you call delete on a pointer, it's a good idea to NULL the pointer
    immediately thereafter.
3.  If you follow rule 2, you can get away with violating rule 1 (!).
4.  You can call a destructor function twice, just be careful about doing so.
5.  Don't take repsponse postings personally, just address the issues.  ;-)
--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Fri, 26 Mar 1993 17:30:22 GMT
Raw View
philr@dspcproj.demon.co.uk (Phil Reynolds) writes:

>Argh...! This still begs the question: Are

>  delete pA;

>and

>  pA->~A();

>the same thing ...


Of course not.  The first is a delete-expression, and the second is
a destructor call.  (Neither the ARM nor the current C++ Committee
Working Paper explicitly allows the second expresion syntax, but let's
assume it is ok.)

A delete-expression invokes a destructor, if any, according to the
static type of the pointer, and then invokes the apropriate version
of operator delete() on the pointer.

Calling a destructor explicitly on a pointer does not invoke
operator delete() on the pointer.
--

Steve Clamage, TauMetric Corp, steve@taumet.com




Author: cok@acadia.Kodak.COM (David Cok)
Date: Fri, 26 Mar 93 20:30:40 GMT
Raw View
In article <1993Mar26.160835.14436@borland.com> eomiya@borland.com (Elliot Omiya) writes:
> ...
>With respect to calling pA->~A at all, this call seems to assume that someone
>else will free the memory for the object.  If the "owner" of pA (the outer
>scope function or object) actually called the new operator then I question
>whether pA->~A should be called in favor of just calling "delete pA."
>However, it makes sense to call the destructor function explicitly (as
>opposed to through the delete operator) if at the current scope, the
>"owner" of pA has been passed this pointer rather than having acquired
>it through the new operator.  In this case, a call to pA->~A can serve
>the purpose of emasculating the object while leaving the memory cleanup
>to someone else.  This must be a carefully planned strategy, however,
>because when the delete operator is called the destructor function will
                  ^^^^^^^^^^^^^^^
>then be called twice.


Be careful with terms:

 a "delete expression" calls a destructor and operator delete
 a destructor converts an object to raw memory
 operator delete releases the dynamically allocated memory
 operator delete does not call the destructor

David Cok






Author: kanze@us-es.sel.de (James Kanze)
Date: 27 Mar 93 12:00:54
Raw View
In article <1993Mar26.173022.22646@taumet.com> steve@taumet.com (Steve
Clamage) writes:

|> philr@dspcproj.demon.co.uk (Phil Reynolds) writes:

|> >Argh...! This still begs the question: Are

|> >  delete pA;

|> >and

|> >  pA->~A();

|> >the same thing ...


|> Of course not.  The first is a delete-expression, and the second is
|> a destructor call.  (Neither the ARM nor the current C++ Committee
|> Working Paper explicitly allows the second expresion syntax, but let's
|> assume it is ok.)

While not formally part of the standard (since they are in the
comments part), several of the examples given on pages 279 and 280 of
the ARM (end of section 12.4) *do* use this syntax, or the equivalent
a.~A().

So it would seem that the intent was to allow it, even if it wasn't
stated clearly.  In addition, the last example cited actually shows
that the intent was in fact to treat all destructors as if they had
the same name (ie: for overloading, etc.).
--
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: steve@taumet.com (Steve Clamage)
Date: Sat, 27 Mar 1993 16:16:31 GMT
Raw View
eomiya@borland.com (Elliot Omiya) writes:


>Now, as for calling a destructor function twice (in contrast to
>destructing an object twice), this may be absolutely OK, or absolutely
>disastrous. ...

>I *believe* that the language specification says nothing about calling
>destructor functions twice ...

The ARM is not explicit about invoking a destructor twice on the same
object, although one could deduce that it is not ok.  (Once the
destructor has been invoked, you no longer have an object, just raw
storage.  A destructor must be invoked on an object.)

The wording in the C++ Committee working paper has been improved to say
explicitly that a destructor may not be invoked twice on the same object.
(That is, the results are undefined.)

--

Steve Clamage, TauMetric Corp, steve@taumet.com




Author: jimad@microsoft.com (Jim Adcock)
Date: 29 Mar 93 20:28:42 GMT
Raw View
In article <1993Mar27.161631.24416@taumet.com> steve@taumet.com (Steve Clamage) writes:
|The ARM is not explicit about invoking a destructor twice on the same
|object, although one could deduce that it is not ok.  (Once the
|destructor has been invoked, you no longer have an object, just raw
|storage.  A destructor must be invoked on an object.)

ARM should be explicit enough for most people:

page 276: "A destructor turns an object into raw memory."

Can a virtual function, including a virtual destructor be invoked
on raw memory?  -- Of course not: the vtable pointer is undefined.





Author: kanze@us-es.sel.de (James Kanze)
Date: 31 Mar 93 14:19:44
Raw View
In article <1993Mar29.202842.16485@microsoft.com> jimad@microsoft.com
(Jim Adcock) writes:

|> In article <1993Mar27.161631.24416@taumet.com> steve@taumet.com (Steve Clamage) writes:
|> |The ARM is not explicit about invoking a destructor twice on the same
|> |object, although one could deduce that it is not ok.  (Once the
|> |destructor has been invoked, you no longer have an object, just raw
|> |storage.  A destructor must be invoked on an object.)

|> ARM should be explicit enough for most people:

|> page 276: "A destructor turns an object into raw memory."

|> Can a virtual function, including a virtual destructor be invoked
|> on raw memory?  -- Of course not: the vtable pointer is undefined.

But what about a non-virtual function?  In particular, if a class has
*no* virtual functions, would it be legal to call the destructor, then
call another function (eventually the destructor) on the object,
knowing how the destructor had left the raw memory.  Or would an
implementation be permitted, say, to null all of the memory in the
object at the end of the destructor?

I don't think the ARM or the current draft of the standard address
this issue.  (I'd generally favor forbidding the practice, on the
grounds that it's probably bad practice anyway.)
--
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: steve@taumet.com (Steve Clamage)
Date: Wed, 31 Mar 1993 18:27:27 GMT
Raw View
kanze@us-es.sel.de (James Kanze) writes:


>But what about a non-virtual function?  In particular, if a class has
>*no* virtual functions, would it be legal to call the destructor, then
>call another function (eventually the destructor) on the object, ...

>I don't think the ARM or the current draft of the standard address
>this issue.

The latest working paper (Jan 28, 1993) is explicit in section 12.4.
See page 12-6, paragraph 10.  Calling a destructor more than once
(explictly or implicitly) on the same object results in undefined behavior.
--

Steve Clamage, TauMetric Corp, steve@taumet.com




Author: detlefs@src.dec.com (Dave Detlefs)
Date: Tue, 16 Mar 93 19:34:50 GMT
Raw View
I can't find any language in the ARM that seems to bear on the
following question:

Obviously, the effect of invoking a destructor twice via "delete", as
in

  A* ap = new A;
  delete ap;
  delete ap;

is undefined, since the storage allocated to the referent of ap was
deleted by the first "delete" and (in a multi-threaded world, anyway)
may have been allocated to some other thread.

However, the ARM explicitly states that it is legal to invoke the
destructor of an object using member function invocation syntax (p.
279).  So, is there any language anywhere in the ARM that says it is
illegal to invoke a destructor twice on the same object?  As in

  A* ap = new A;
  ap->~A();
  ap->~A();

I hope the answer is no; that answer allows one the model where the
destructor is just a member function that is invoked automatically at
certain times (end of blocks, point of "delete"), and conventionally
has a very weak specification -- it usually ensures no properties of
the object is invoked on.  But I think it would be better if these
were just conventions, orthogonal to the semantics of the language.

This question has a practical impact; if the program above is legal,
and should have a well-defined meaning when the destructor of A has a
well-defined specification, then compilers have to take care to
restore the "vptr" of "ap" if A has virtual functions and base
classes.  (During destruction, you have to "swing" the vptr to point
to a base class' vtable before invoking the base class' destructor.)

I'd appreciate any well-informed comments on this issue...

Dave










Author: eomiya@borland.com (Elliot Omiya)
Date: Wed, 17 Mar 1993 21:34:46 GMT
Raw View
Well-informed?  Well, perhaps not, but consider that the code you give in
your article is of the "nasty code from hell" variety, equivalent to freeing
malloc'ed memory twice.

What *is* legal is the following:

objType *myObj;

myObj = new objType;
delete myObj;
myObj = NULL;
delete myObj;

because calling delete on a NULL object pointer is guaranteed to do nothing.
However, I can guarantee that deleting an object twice will wreak havoc in
your program, and in a place that is nowhere near where the second (erroneous)
destruction occurred.

Moral to the story, always NULL object pointers after object destruction.

Go into your favorite debugger and watch what happens to the vtable after
object destruction, this is very instructive.


--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------




Author: detlefs@src.dec.com (Dave Detlefs)
Date: Wed, 17 Mar 93 23:36:06 GMT
Raw View
Elliot --

Thanks for replying, but I think you've missed the point of my
question:

> Well-informed?  Well, perhaps not, but consider that the code you give in
> your article is of the "nasty code from hell" variety, equivalent to freeing
> malloc'ed memory twice.

I said

> Obviously, the effect of invoking a destructor twice via "delete", as
> in
>
>   A* ap = new A;
>   delete ap;
>   delete ap;
>
> is undefined...

I was giving this as an example of obviously bad code, but the status
of the other code fragment

  A* ap = new A;
  ap->~A();
  ap->~A();

is much less clear.  This doesn't delete any storage, it just runs the
destructor twice on the object to which ap points.  The question I had
was whether this is legal.  It's obviously legal syntax; the question
is whether if A has a vptr and a base class, will A's vptr be restored
to its original value after each destructor call.  Rather to my
surprise, at least the two compilers I could lay hands on, g++ and DEC
cxx, handle this in the way I think is correct, as shown by the
following program and sample output:

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

class T1 {
  int j;
  virtual void foo() { cout << "In T1::foo().\n"; }
public:
  T1() { j = 0; }
  ~T1() { cout << "In T1::~T1().\n"; foo(); }
};

class T2: public T1 {
  int i;
  virtual void foo() { cout << "In T2::foo().\n"; }
public:
  T2() { i = 0; }
  ~T2() { cout << "In T2::~T2().\n"; foo(); }
};

int main() {
  T2* t = new T2;
  t->T2::~T2();
  t->T2::~T2();
  delete t;
}
----------------------------------------------------------------------
In T2::~T2().
In T2::foo().
In T1::~T1().
In T1::foo().
In T2::~T2().
In T2::foo().
In T1::~T1().
In T1::foo().
In T2::~T2().
In T2::foo().
In T1::~T1().
In T1::foo().

Note that the call to the virtual function "foo" from ~T2 always gets
T2::foo.

Dave







Author: steve@taumet.com (Steve Clamage)
Date: Thu, 18 Mar 1993 20:04:28 GMT
Raw View
detlefs@src.dec.com (Dave Detlefs) writes:

>I was giving this as an example of obviously bad code, but the status
>of the other code fragment

>  A* ap = new A;
>  ap->~A();
>  ap->~A();

>is much less clear.  This doesn't delete any storage, it just runs the
>destructor twice on the object to which ap points.  The question I had
>was whether this is legal.

The ARM is missing the explicit wording that says the results of
invoking the destructor on the same object more than once are undefined.
Clearly that must be the case, since like any member function, a
destructor may only be invoked on an object of its type.  Once the
destructor runs, the object no longer exists.

The C++ Committee working paper for the standard includes the explicit
wording as above.

So, the answer is, "No, you can't do it."
--

Steve Clamage, TauMetric Corp, steve@taumet.com




Author: eomiya@borland.com (Elliot Omiya)
Date: Fri, 19 Mar 1993 21:08:15 GMT
Raw View
Sorry, I didn't mean to be insulting or anything.

The second example is just as bad as the first and points out (oops, bad pun)
the danger of using pointers to objects.  The pointer still has a value (i.e.
points to an object that used to exist) but the destructor will *think* it is
doing something legitimate, but in fact it is still freeing memory twice.

One safe thing to do is as I wrote in the first article:

ap->~A;
ap->A = NULL;

A second (inadvertent?) call to ap->~A will do nothing.

Unfortunately, the *syntax* of the second call to the destructor is correct
so the compiler cannot know that it is illegitimate.

--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------




Author: eomiya@borland.com (Elliot Omiya)
Date: Fri, 19 Mar 1993 21:59:29 GMT
Raw View
With respect to your code fragment example, this is a little different from
the subject I was addressing, so I apologize.  They appear to be the same but
that is (I believe) due to some unfortunate syntax in the language.  (Sigh,
it seems impossible to know everything about this language!)

Consider the following modifications to your program (I did this in BC3.1):
---------------------------------------------------------------
#include <iostream.h>

void main(void);

class T1 {
  int j;
  virtual void foo() { cout << "In T1::foo().\n"; j++; }
public:
  T1() { j= 0; cout << "T1 constructor\n"; }
  ~T1() { cout << "In T1::~T1().\n";
     foo();
   cout << "j = " << j << endl; }
};

class T2: public T1 {
  int i;
  virtual void foo() { cout << "In T2::foo().\n"; i++; }
public:
  T2() { i = 0; cout << "T2 constructor\n"; }
  ~T2() { cout << "In T2::~T2().\n";
     foo();
     cout << "i = " << i << endl; }
};

void main()
{
  T2* t = new T2;
  t->T2::~T2();
  t->T2::~T2();
  delete t;
}
---------------------------------------------------------------
This gives the following results:

T1 constructor
T2 constructor
In T2::~T2().
In T2::foo().
i = 1
In T1::~T1().
In T1::foo().
j = 1
In T2::~T2().
In T2::foo().
i = 2
In T1::~T1().
In T1::foo().
j = 2
In T2::~T2().
In T2::foo().
i = 3
In T1::~T1().
In T1::foo().
j = 3
---------------------------------------------------------------
What is happening in this example is that the self-referential destruction
call t->T2::~T2 is **not** resulting in the destruction of the object
pointed to be t, it is merely calling the function that happens to have the
name ~T2.  This is the "unfortunate" syntax I referred to above.  I didn't
refer to the ARM to find out if the syntax is legal (it must be, it works in
three different compilers) or what the reason for it is, but one thing is
clear, the object pointed to by t isn't getting destroyed until the "delete t"
is called.

Interesting question.
--
-----------------------------------------------------------------------------
Elliot H. Omiya, KC6DAL     Borland International Inc.  -=<EHO>=-
-----------------------------------------------------------------------------