C++0x Garbage Collection

K

Kaz Kylheku

Ian said:
That should read "prevent errors introduced through lazy programming"

Garbage collection allows for certain programs to be expressed which
are impossible without it, because they would collapse under the
complexity of managing the memory.

He is lazy who avoids work in order to be idle.

He is not lazy who avoids work in order to do other, more valuable
work.

If you think that automaticaly managing object lifetimes is laziness,
then never define a smart pointer.

You know, those constructor and destructors, all they do is prevents
errors introduced through lazy programming. Real programmers always
remember to call the right initialization function after allocating the
right amount of memory.

I invite you to take the Maxima project, a computational algebra system
written in Common Lisp, and rewrite it in C++, with no smart pointers
whatsoever for managing memory. Ha ha.
My platform has an effective GC library, but I only use it during
acceptance test runs, to verify that there aren't any leaks.

GC doesn't belong in the language, if it is to be used at all, it should
be in a library.

GC isn't anywhere. There is no new syntax, and no function to call.

If you have special GC-related features like weak pointers, or an
explicit call to the garbage collector, then you need an extra library.

It does make sense for the GC run-time support to be optional in C++
implementations. Many C++ implementations make exception handling
support optional, and run-time-type-identification optional.

Don't pay for what you don't use; makes sense.
RAII is a much more effective and deterministic tool.

RAII is limited to block scopes. It does nothing for dynamically
allocated objects.

C++ programmers sometimes use RAII to implement memory management via
smart pointers (e.g. reference counting). That is no longer
deterministic; you don't know which instance of the smart pointer will
take the object with it when it is destroyed.

And, besides, RAII is for the mentally weak and lazy, right? Real,
non-lazy programmers always remember to clean everything up at every
exit point from the function, and deal with all possible exceptions.
 
A

Alf P. Steinbach

* Kaz Kylheku:
That is too naive.

Thinking you have solved the problem of coupling garbage collection to
object destruction deserves some label, but not a factual statement.
 
I

Ian Collins

Kaz said:
Garbage collection allows for certain programs to be expressed which
are impossible without it, because they would collapse under the
complexity of managing the memory.

He is lazy who avoids work in order to be idle.

He is not lazy who avoids work in order to do other, more valuable
work.

If you think that automaticaly managing object lifetimes is laziness,
then never define a smart pointer.
I use them all the time.
 
P

peter koch

Roland Pibinger skrev:
On Tue, 27 Jun 2006 05:07:45 +0200, "Alf P. Steinbach" [snip]
When someone wanted to introduce GC into C++ with a newly-made 'gcnew'
operator the _compiler_ would have to check if all data members of the
gcnew-ed object (and its base classes) have trivial (ie.
non-implemented) destructors. Otherwise the compiler would have to
issue a compile time error. This means that not even the current
std::string could be a member of a gcnew-ed object.

Why a compile-time error? So far as I know it is perfectly conforming
to leak resources. Also, if you were a bit more modest and required an
all-or-nothing approach and used a garbage collected new rather than a
special purpose gcnew, the destructor of std::string would be empty
(disregarding the "noop" call of delete), and there would be no err...
ehm ... warning.

/Peter
Moreover, since
objects with trivial destructors usually are of value type

What do you base that statement on? Actually this is mostly my
experience to, but if you look at the threads referred to by Jerry
Coffin this certainly can't be the experience of souls like
Alexandrescu and Sutter.
and value
types are best handled with value semantics the introduction of even a
limited form of GC into C++ seems highly questionable.

No need to.... Boehm has this nice library that does a lot of what you
need.
Best regards,
Roland Pibinger

/Peter
 
I

I V

std::string could be a member of a gcnew-ed object. Moreover, since
objects with trivial destructors usually are of value type and value

The obvious exception would be types designed to be used polymorphically,
which probably have trivial destructors, probably aren't value objects,
and can't be used with value semantics.
 
R

Ron House

Kaz said:
The most straightforward way is for the garbage collector to know how
to invoke the equivalent of delete on the objects. (I would abhor a
scheme whereby GC'able objects have to inherit from some special base
class with a virtual destructor; it would be better if this was
intelligent somehow).

For some types of objects, calling the destructor at GC time might be
too late.

That's the reason finalisers aren't the equivalent of destructors, and
the reason I ask if destructors can be combined with GC - without being
replaced by finalisers.

For example, is it possible to write a GC that cleans up immediately the
last pointer disappears, rather than in a general sweep, and still be
acceptably efficient? Note: I have looser ideas about what's efficient
than a lot of people, within reasonable bounds. Turning C++ into a
hyper-reliable (defensive against programmer error) language is much
more important to me than cpu cycles.
>...
If you don't have garbage collection or some other scheme, you still
have to compute the lifetime of an object and call for it to be
destroyed by explicit delete.

That's why I want GC - _and_ destructors (not finalisers).
You simply have to regard that operating system resource handle has
having its own lifetime, which is contained within the lifetime of the
encapsulating object.

Do we really? I know this is usually claimed, but is there a proof or
does it merely seem 'obvious'?
You can take the responsibility for computing that contained lifetime,
and let the garbage collector determine the main lifetime.

That at least is safe. But sometimes it make the coder turn sommersaults
to get things working right. Sometimes a module receives an object and
doesn't know whether it must be deleted or not. That means that we
always have to have programming designs where each module takes care of
its own. Comparison of this with the sort of thing possible in, for
example, Haskell, shows how restrictive this can be.
You don't want to use the destructor for ending the resource lifetime,
because that will turn your entire object into garbage, while it is
still reachable.
So the obvious thing is release the resource and change the state of
the object to indicate that the object does not have that resource.
Garbage collection is not incompatible with your program knowing when
to release a resource.
>...

Your example of BEFORE and AFTER is interesting. I had wondered about
that idea and wasn't sure it really is useful. Your example is a good
one. It is not clear to me that it solves this problem without requiring
app-specific code. We might not want to change the compiler, but we do
want to at least have classes that make everything automatic.
 
R

Ron House

Kaz said:
Under GC discipline, the destructor must be regarded only as a last
resort cleanup for these kinds of resources.

But this is exactly my question: Is it really a "must" or are we all
just assuming it's a must because we haven't spotted the way to do it
yet? Has anyone proved that it really is a must?
 
A

Alf P. Steinbach

* Ron House:
Your example of BEFORE and AFTER is interesting.

It's mostly bull. Kaz advocates zombie objects, like in Java. The idea
is that by introducing enough complexity, and by posting enough barely
different follow-ups, people will be smothered by details and not be
able to argue against (and I for one will not reply to every posting).
 
K

Kaz Kylheku

Ron said:
That's the reason finalisers aren't the equivalent of destructors, and

The general reason why destructors aren't the same thing as finalizers
is that they live in different programming languages.
the reason I ask if destructors can be combined with GC - without being
replaced by finalisers.

For example, is it possible to write a GC that cleans up immediately the
last pointer disappears, rather than in a general sweep, and still be
acceptably efficient?

So your definition of a destructor appears to be: something which runs
just after the pointer disappears. Whereas a finalizer is something
that runs just before the object is scavenged and re-entered into the
free store.

The problem is that the C++ destructor meets the latter definition a
lot more closely than the former.

Why is that event interesting when the program erases its last pointer
to an object?

I would argue that an interesting event is when the program loses the
last pointer to an object from an interesting subset of all the
pointers to that object.

Pointers which are not in that set behave semantically like weak
pointers.

For instance, suppose you have implemented some cache of objects from
which they expire based on some aging scheme. That cache has poitners
to the objects, of course. However, when /only/ that cache has a
pointer to some object, then, semantically, that object is practically
as good as garbage. If nobody grabs it before the time comes up, it
will be scavenged.

Moreover, certain actions may have to be taken when the object is
entered into the cache to be expired.

These kinds of schemes are found in C++ programs.

They are sometimes combined with reference counting too. I remember
working on systems where it was known that a certain module held a
reference to an object. Therefore, some clean up actions were triggered
on, guess what, the 2 -> 1 refcount transition. A notification was sent
out through the framework, and then that special module would drop
/its/ reference, which would trigger the destructor and delete.

So in other words, the 2 -> 1 refcount was the real disappearance of
the object, and that module effectively held only a weak pointer. Of
course there was nothing manifestly different about the reference that
it held; it was all in the semantics.

Aren't destructors being used for finalization in these situations?

Formally, what is a finalizer? It is a special entry created in the
memory manager which holds a weak pointer to some object, and a
function which is to be called when /only/ weak pointers to the object
remain, prior to that object being reclaimed.

That function itself is sometimes called a finalizer, transitively.
There is no reason why a C++ destructor cannot serve as that function.
That's what it's for: to perform the last rites on an object.

I think what you're asking for is to have an additional notification
when the program loses its last (non weak) reference to an object.

But computing that notification is the same thing as knowing that the
object's lifetime has ended. You call the destructor, and so you might
as well re-enter the object into the free storage. Once the destructor
has run, the object its reduced to just being memory. You can't do
anything with it, and so there is no point in registering any other
function on it.

Therefore, it doesn't make sense to want 'destructors /and/
finalizers'. It does make sense to have the choice to have delayed
/and/ timely finalization, for different objects.

To make use of that choice, it's necessary to have weak pointers. To
have a resource cleaned up in a timely way, the program has to be able
to indicate that certain references to the encapsulating object are
weak.

Then if the program enables that "just in time" garbage collection, it
will get the finalization trigger as soon as the last non-weak
reference is lost. The object is destroyed and all of the weak
references lapse into null values.

This still leaves the problem of what to do if the program design wants
to only have the resource cleaned up, not the entire object: to have
the resource cleaned up at some point when the object is still
reachable by ordinary non-weak references. The object continues to be
used without that resource. Then the program its on its own anyway.

What it boils down to is this. You have some object O which holds a
resource handle R. the object O is shared by multiple references to it.
A module can either hold a reference to O, or not hold a reference to
O. Only thorugh its reference to O can a module express its interest in
resource in R. The problem is that holding or not holding a reference
is only a boolean value: interest yes, or interest no. But there are
two entities there, O and R. The boolean interest value doesn't hold
enough information to express interest in these two separately.

What the program needs is a way to express two kinds of strong
references:

- references which express interest in O with or without the resource
R.
- reference which express interest in O with the resource R.

Now when there are no more references of the second kind, when the only
references to O that remain are weak references and strong references
which do not care whether R is valid, the resource R can be deallocated
at that point.

How can you implement such a scheme? One way is to implement the second
type of reference, expressing, "interest in O with the resource R",
using reference counting. What you can do is allocate a second object,
call it P, which holds a reference count, a reference to O, and a
method to invoke when the count hits zero. Modules which are interested
in R manipulate pointers to P instead of O. By holding references to P
instead, they express a special interest in O related to the meaning of
the reference count in P.

Modules manipulating P take care to manage the reference count among
themselves: when they copy the pointer, they bump up the refcount, and
when they erase it, they drop the refcount.

When the refcount hits zero, the special method is run on P, which does
something with object O: namely, it destroys resource R, and replaces
that handle with an invalid value, an action that can be encapsulated
in a method on O.

So provided that the refcounts are accurately managed, and the
appropriate kind of reference is used by every module to express the
correct kind of interest, the resource will be accurately managed.

No help is needed from the garbage collector; it can delay
finalization.

Or, instead of refcounting, a special memory arena could be used for
these P objects, an arena where garbage collection is greedy: it tracks
the lifetime of these objects accurately. When all the references to P
object disappear, it is reclaimed right away. Its finalizer action
performs the resource cleanup on P. So the effect is like that of
reference counting.

These P objects could hold references to each other, denoting
hierarchies of interest. Suppose that object P1 expresses interest
related to resource R1 in object O. Object P2 express interest related
to resource R2 in that object. How do you express total interest in O
having both resources? By a third object P12 which holds a reference to
O, and also to P1 and P2. The cleanup method of P12 does nothing with
O. All it does is null out P12's references to P1 and P2. If doing so
erases the last reference to P1, then P1's cleanup action is triggered,
releasing the resource O.R1. Likewise for P2.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top