Larry said:
It's a religious issue and both have their pros and cons. While C++ may be
more error-prone than a GC system however (for releasing resources), there
need not be a significant difference.
I certainly agree there. But one system is definitely more resilient to
programmer error.
The problem is almost entirely a human
one. It's extremely easy to handle resources in C++ as you stated but the
language's reputation has often suffered because of its practioners (most
programmers being very poor at what they do). My own opinion however is that
in the hands of those who really know what they're doing (few and far
between),
I think we've found ourselves in vehement agreement on that point
previously. I'm still in vehement agreement with you on it.
RAII is a cleaner approach than a GC system. By clean I mean it's
more natural to release your resources in a destructor than to wait for a GC
to run (not to be confused with easier or less error prone - it's clearly
not).
Well, the thing is...once you no longer have a reference to a memory
resource, it _is_ released. Just because the GC hasn't run, that
doesn't mean the memory isn't available. It just means that the GC
hasn't gotten around to moving it into the collection of memory that is
_immediately_ available for use.
Logically speaking, the memory is still in fact available, the moment
you release your last reference to it.
The "using" statement for performing this in C# for instance (or even
worse, a "finally" block), is ugly compared to a C++ destructor.
IMHO, it's a mistake to think of the "using" statement, the finalizer,
or IDisposable as related to a C++ destructor. It's only "ugly"
compared to it if you are treating them the same.
The "using" statement and IDisposable exist for one purpose: to
explicitly release resources held by an object without releasing the
object itself. Within that one purpose, there are two sub-categories of
types of resources that may be released: managed, and unmanaged.
Obviously the only reason the unmanaged category even exists is that
..NET runs on top of a system that is not entirely managed code. If all
of Windows was based on a garbage collection system, that category
wouldn't exist.
So, let's consider only the managed category. In this case, it's
beneficial to be able to tell an object "let go of the resources you're
holding" without releasing that object itself. But this is again not
comparable to a destructor, because the object itself still exists. It
hasn't been released or destroyed and it is theoretically possible that
it could be reused. This is much more comparable to a C++ class that
hasn't been deleted, and thus hasn't been destroyed, but which has some
sort of "release your resources" function that has been called.
The destructor is nicely symmetrical with its constructor.
The latter initializes the object and the former cleans it up.
And in C# not having a reference to an object is nicely symmetrical to
creating a reference to an object. The latter initializes the object
and the former cleans it up. In a purely managed environment, releasing
a single reference to an object is exactly equivalent to the C++
paradigm of having to call a destructor where individual resources
within the object have to be explicitly cleaned up.
In fact, the garbage collection model is, at least for that particular
operation, much more efficient, because there's no need to go through
the entire object cleaning things up. Everything that object refers to
is automatically released, with a single nulling, or leaving scope, of
the last variable holding a reference to that object.
Overall, I suspect the efficiency is about the same. The extra work
that the C++ model has to do initially is balanced by the extra work the
garbage collector will have to do later.
This occurs immediately
when an object goes out of scope so your resources only exist for as long as
they're needed.
Likewise, using GC as soon as an object is no longer referenced, any
managed resources no longer exist. They are automatically released when
that object referencing them is.
It's all very well controlled and understood. You know
exactly when clean up is going to occur and need not worry the timing of a
GC.
But why do you care when the GC is going to occur? It only happens when
it needs to, or when it gets the opportunity to, and there should be
nothing in your code that depends on or otherwise relies on when, if at
all, garbage collection happens.
In a multi-tasking operating system like Windows, there are a wide
variety of things that occur and which you have no control over. A
garbage collection system simply introduces a new instance to this
already very broad category of components.
In fact, a GC itself can even promote sloppy behaviour. People become
so used to it doing the clean up that they can neglect to explicitly clean
something up themselves when the situation calls for it
I don't understand that at all. A person who isn't used to releasing a
reference to an object when they are done with it isn't going to be used
to deleting a C++ object when they are done with. Conversely, a person
who can remember to delete a C++ object when they are done with it can
remember to release a reference to a .NET object when they're done with it.
(such as immediately
releasing a resource that might later cause something else to fail if it
hasn't been GC'd yet).
What kind of resource? If you're talking about a managed resource, then
simply releasing the reference to the referencing object is sufficient
to release the resource.
If you're talking about an unmanaged resource, well...that's not a
problem inherent with garbage collection. It's a natural consequence of
mixing a garbage collection system with a traditional alloc/free system.
That problem _only_ exists because of the traditional alloc/free
system; it hardly seems fair to blame it on the garbage collection paradigm.
Or people might always perform shallow copies of
their objects where a deep copy is required (since it's just so easy).
This one I understand even less. If a deep copy is required but a
shallow copy is done, this is if anything more dangerous in the C++
model, because the referenced data can be freed by any one copy of the
instance. This just won't happen in a garbage collection system.
With a GC system, you still have the potential issue of having multiple
instances refer to the same data, but this issue exists regardless of
the memory management model. It's an implementation problem, not a
memory management problem.
In
C++ you have to think about these things more but that deeper thinking
process also sharpens your understanding of the issues IMO (and hopefully
the design of your app). Of course this is all a Utopian view of things. In
the real world most programmers require the handholding that a GC offers.
I generally agree that it is good to think more deeply about what is
going on. A person who understands better the lower levels is almost
always going to be able to use use the higher level API more
effectively. But I don't see how that makes the C++ model necessarily
better; either model has some lower level implementation details that
are important to understand for most effective use, and C++ has just the
same potential for someone failing to bother to learn those lower level
implementation details as .NET does.
And I still think that people scoff at garbage collection at least as
much as they do the more traditional C++ model.
Pete