Christopher Smith wrote:
leave scope. While it accomplishes much the same thing, it doesn't allow
this cleanup work to be encapsulated in the object (you have to write it
out each time).
Yeah, that's one of the nice things C# cleaned up:
using (x = new X())
using (y = new Y())
{
x.this();
y.that();
}
x and y's finalizers will get called at the end of the block.
It messes up in that if the "new" throws an exception, the finalizer
doesn't necessarily get called right there, but it's still better than
Java or C++.
Destructors in C++ don't necessarily get called unless you can actually
instantiate the object on the stack anyway, while finalizers do
eventually get called.
And most programs where you're worried about this stuff have an obvious
place where you can invoke finalizers, like the start of each frame in a
video game, or the end of each transaction in a database server, so you
can just do a GC at that point and force the issue. Or do a GC at each
top-level catch of an exception, and you have a pretty well-defined
place for finalizers to run.
The "Connection" object's destructor cleans up the connection as best it
can when conn drops out of scope.
Assuming it doesn't throw an exception in the constructor.
unwind, the program will terminate if conn's destructor gets invoked as
part of the unwind *and* it throws an exception.
log.error("Error when closing connection......");
Better hope log never throws an exception...
the parent destructors will get invoked after you've finished your work,
and there's not much you can do to stop it beyond crashing.
Which is not always what you want either.
The solution is to make A::~A() virtual.
The thing that always kills me about C++ is the advice "always do X",
and then X isn't enforced in the language, even tho it's always wrong
not to do X.
Anyway, in practice, finalizers prove not to be that useful for most
cases, and also a source of much additional complexity (though
thankfully in the common case most of the complexity is in the hands of
whomever has to write the memory manager) and destructors prove to be
quite useful, particularly for managing non-memory related resources,
but are also a source of much additional complexity.
I think it depends what you're used to. I never had a problem with
finalizers. Also, when you're using an OS that has support for these
kinds of things, it becomes pretty transparent. When you have an OS that
(for example) will run all finalizers when you try to open a file and
it's busy or you're out of handles, things work much more smoothly. Just
like when you have an OS that'll run a GC when you get low on memory.
If you think of finalizers as GCing stuff that the OS isn't GCing for
you, it makes more sense. With the appropriate syntax, it's neither
harder nor easier than destructors to get right.
--
Darren New / San Diego, CA, USA (PST)
It's not feature creep if you put it
at the end and adjust the release date.
--
[email protected]
http://www.kernel-panic.org/cgi-bin/mailman/listinfo/kplug-lpsg