I provided both the subthread and the keyword "zombie" so that
you could review some of the practical isses I'm referring to.
Did you review that thread? Or follow it at the time?
I might have followed it at the time; I do remember some
signification threads on this subject (in which Andrei was a
contributor). I've not had time to read the complete thread
now, however. But unless it presents some radically new
information, I'm fairly aware of the issues.
It's also not a resolved topic which you implied with your
emphatic "Attention!" statement.
I think it is, actually. At least within the context of C++;
the standard makes a very clear distinction. I'm also aware of
people raising similar issues with regards to other languages,
but I don't know how widespread the issues have been discussed
or resolved within those languages; I've had some very concrete
discussions about this concerning C#, but the person involved
(Herb Sutter) comes from a C++ background, which may have
colored his understanding.
The notion of "lifetime of an object", as described in the C++
standard, is, IMHO, fundamental to good software engineering.
Regardless of whether it is embodied in the language or not.
(As far as I know, C++ is the only language which really makes
this distinction. Although perhaps Ada... offhand, it seems
very difficult to define a constructor and even more so a
destructor without it.) One important point with regards to
garbage collection is that it doesn't affect all objects; most
objects don't need an "active" destructor. The other important
point is that some objects do, and that doing anything with such
an object after its destructor has been called (or whatever
function is used for this; in some Java circles, at least,
dispose() seems to be the preferred name) is a programming
error.
And what does "ceased to exist" mean?
That the object has been "deconstructed". That it no longer
exists as an object of type T, in the sense that it is no longer
capable of behaving as a T should, and that using it as a T is a
programming error. In classical C++, the destructor has been
called, but of course, in other languages, some other convention
may be usual.
If the object does not "exist" then what is the pointer
pointing to?
That is a good question. Nothing really. Or raw memory.
Formally (i.e. according to the C++ language definition), it
doesn't matter, since dereferencing the pointer would result in
undefined behavior. (There is a special exception here for
reading the object as an array of bytes. It don't think it's
really relevant; an array of bytes is not the object, but just
the raw memory.) In practice, of course, as we all know, if a
pointer to something exists, even if that something no longer
exists, it will be used, sooner or later.
How about we give the "it" (even though "it" does not "exist")
that the pointer points to a name. Hmm ... I know, let's call
it a "zombie". And there begins an entire /unresolved/ debate
that you can review in the link I provided.
Let's call it raw memory. That's what the C++ standard does.
And regardless of what is in that discussion (I'll try and find
time to read it, but it looks very, very long), the issue is
resolved and closed as far as C++ is concnerned. After the
destructor has been called, but before the memory has been
freed, you do not have an object. You have raw memory. (Since
you're accessing it via a T*, it is sometimes convenient to call
it a T object with a special, zombie state, but this is really
misleading.)
Do you know of a GC system/semantics that resolves the issues
raised in that thread? Can you elaborate on its semantic
model?
Well, I'll have to read the thread to be sure, but the point of
garbage collection here is that with garbage collection, the raw
memory left after the deconstruction of an object will not
(normally) change its state as long as a pointer to that memory
exists anywhere in the program. Thus, if the object was
polymorphic, the destructor can "stomp" on the memory which
contained the vptr, and any attempt to call a virtual function
is guaranteed to result in a core dump. If the memory has
already been freed, however, it may have been reallocated, and
an object may have already been constructed in it, with,
perhaps, the vptr of the new object in exactly the same place.
My personal recommendation for robustness would be to clearly
analyse, at the design phase, which objects needed
deconstruction, and which didn't, without regards to memory
management. For the latter, once design has determined that
the destructor is trivial, i.e. that the object can be logically
used as long as it is accessible, just leave it to garbage
collection. This is probably the most frequent case, but most
of the objects so designated won't ever be allocated
dynamically, either. But there will be some. For those objects
which must be deconstructed, which have a determinate lifespan,
the deconstructing function must be called at a deterministic
moment. In C++, I do this by means of the delete operator,
using the destructor as the deconstructing function; when
garbage collection is present, I replace the global operator
delete by one which overwrites the memory with some easily
identifiable pattern, which (probably) can't be a pointer to
anything valid---0xDEADBEEF is a good choice for 32 bit
machines, I think. In Java, in such cases, I simply add a
boolean variable to the base class type, which the constructor
sets true, the destructor---sorry, the dispose() function---sets
false, and I test it at the top of each function. (You'll
notice that the C++ solution is a lot less work
.)