[gclist] Baker Treadmill as an alternative to reference counting.

Jerrold Leichter jerrold.leichter@smarts.com
Tue, 5 Sep 2000 14:27:20 -0400 (EDT)


| However, I regard finalizers as a hopelessly flawed concept regardless of
| implementation and that includes reference counting. If the collector
| invokes a destructor that's a finalizer. The point of collection is that
| when an object can no longer influence execution its deleted. A finalizer
| says, "Now that we know this object can no longer influence execution lets
| run a finalizer on it and influence execution." Another way to state that
| is the collector goes to the mutator and says "Here's a dead object."

This is a biased view of destructors, and the misunderstanding causes problems
on both sides of the divide.

A destructor isn't executed on a dead object.  It's executed on an object
that's just about to undergo the live/dead transition.  Before the destructor
executes, the object was live; while the destructor executes, the object is
live; after the destructor finishes, the object is dead.  The destructor has
no way of preventing the final transition to the dead state.  (In C++ - to
which the semantics of destructors is central - you can, of course, retain a
pointer to a dead object.  You could even save such a pointer from within the
destructor.  But any use of that pointer, once the destructor has returned, as
designating anything other than raw memory produces undefined results.)

This is quite different from a finalizer, which really is handed a dead object
(which in some systems it might even resurrect).

Note that the "advanced" uses of destructors in C++ - those that allocate (de-
allocate) resources in constructors (destructors) - are making use of exactly
this "get in on the transition" semantics.  It's fundamental to what makes them
useful.

To have destructors, you have to be able to detect that an object is about to
transition from live to dead.  For the three lifetimes in C++ - entire program,
manually controlled (new/delete), stack frame - this is straightforward.  A
straightforward RC implementation (no "lazy reference counts" to try to keep
work bounded) also has access to this information.  A GC implementation does
not, by its very nature (unless you run a GC each time a pointer to an existing
object is changed).  Destructors simply make no sense for languages that
support "in-use" lifetime, unless you can constrain the semantics in some
other way (e.g., linear languages).  Finalizers can make sense, as long as you
are careful in what you allow them to do.

Finalizers and destructors are just different things, and trying to use one
to do the job of the other is guaranteed to get you into trouble eventually.

							-- Jerry