[...]
I don't have a fundamental problem with GC. However, I find it
difficult to use an approach that combine both RAII and GC. The
non-deterministic nature of GC mostly breaks RAII. I'd welcome
enlightment but at the moment, the only way I would find C++ with GC
of benefit would be if I was not using RAII at all and if the only
resources used was memory.
I think the key is to forget about "resources" (as something
special) for a moment, and think in terms of object lifetime.
Different objects have different constraints with regards to
lifetime:
-- For value oriented objects, it's largely irrelevant. The
same is true for a certain number of what I call
agents---objects which (typically) have no state of their
own, but exist only to do things on other objects (visiters,
observers, etc., which "connect" different objects).
Generally, these objects are allocated on the stack, and
copied as needed into the appropriate scope; the exceptions
are when agents need to be polymorphic, or when the objects
are too expensive to copy. Garbage collection is the
simplest solution for these, although normally, shared_ptr
also works fairly well.
-- Entity objects, whose lifetime is controlled by the
application semantics. Sometimes, they can just silently
die, in which case, garbage collection is all you need.
Most of the time, however (at least in my applications),
there has been a need for some sort of definite "death"; the
objects can't (or shouldn't) be used after this death, and
there may be specific behaviors associated with this death.
Neither garbage collection nor RAII (shared_ptr or other)
help in managing this lifetime, since it is determined by
events outside the program. And it's almost always
necessary to use some variant of the observer pattern to
notify all interested parties when its death occurs. (In
some simple cases, a combination of weak_ptr and
a shared_ptr to self in the object might be doable, but the
results are at least as complicated as a manual
implementation of the observer pattern, and can easily be
broken by future evolutions.) In such cases, garbage
collection can be used to improve robustness: the underlying
memory cannot be used for anything else as long as any
pointers to it exist (which shouldn't be the case, but
errors do happen), so you can mark it in some way, and test
for validity at the start of each member function.
-- Objects which manage external resources. These are the few
objects for which the term RAII is really appropriate. They
will normally be non-copiable, and in my experience, almost
always local variables. For the rare cases where local
variables are not appropriate, shared_ptr is (and not
garbage collection).
This is, of course, very general, and there are doubtlessly
other special cases which will occur. Still, my point remains:
you need to understand the role and the responsibilities of the
class (or the class hierarchy) before doing anything with the
class, and once you've done that, the choice of leaving it to
garbage collection, using shared_ptr, never allocating it
dynamically, or yet some other strategy, can be made.