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

Reply via email to