LWN.net Logo

What, again?

What, again?

Posted May 2, 2007 0:58 UTC (Wed) by bronson (subscriber, #4806)
In reply to: What, again? by peterh
Parent article: The Rise of Functional Languages (Linux Journal)

What does a destructor provide that finalize() doesn't? That's easy: a definite time that the cleanup code is called. This argument is ages old. Briefly:

    for(log files) {
        File f;
        f.open(file name);
        f.quick_scan();
    }
In C++, you will only have a single fd in flight because the destructor is called and the file is closed the moment the block is exited. In Java (older versions; I haven't used Java recently), you'd typically bomb out after a few thousand iterations because you exhaust your file descriptors before a garbage collection cycle can be run.

Most resources are of such a limited quantity that you need to know with confidence exactly when they are released. Destructors provide this, finalizers don't.

For instance, do you see the problem with the following code? It's only a problem if your language uses finalizers.

   try {
     FileReader f = new FileReader(filename);
     process(f);
     r.close();
   } catch(IOException e) {
     ...
   }


(Log in to post comments)

What, again?

Posted May 2, 2007 1:34 UTC (Wed) by peterh (subscriber, #4225) [Link]

Yes, but you have _exactly_ the same problem in a language with explicit deallocation and destructors.

If you use finalizers to free resources, you'll have these sorts of problems. Use a free_my_resources() method for that. If you forget to call it, you'll leak the resources (but the finalizer could check for that, at least limiting the damage). If, on the other hand, you forget to call "delete" on the object, you leak the resources and the object itself. So you have to do something explicit either way, and without garbage collection the damage is worse (you have a memory leak into the bargain).

Not quite sure how that's an improvement, myself.

There is an exception here --- if your object is allocated on the stack (which Java forbids). This does get you a kind of "auto-destruction" semantics. But is this a common case?

What, again?

Posted May 2, 2007 3:42 UTC (Wed) by ncm (subscriber, #165) [Link]

It has been almost ten years since I coded a "delete" statement in C++. I can't forget to write them because I never needed to write them in the first place. How does memory get freed? Destructors free it. I don't write calls to destructors, because the compiler writes those calls itself.

That's what is meant by "encapsulating resource management in libraries": all the work is coded in the library, and then you just use it. There is no value at all in the language providing "memory-safety" when the language is powerful enough to allow it to be encoded in libraries in exactly the forms needed. A language not powerful enough to encode resource management in a library looks useless to me.

Finalizers are little more than a cruel joke, and do not merit discussion in this context.

What, again?

Posted May 2, 2007 4:41 UTC (Wed) by foom (subscriber, #14868) [Link]

...just so long as you never have circular references.

What, again?

Posted May 2, 2007 17:06 UTC (Wed) by dcoutts (subscriber, #5387) [Link]

So you are exclusively using blocked scoped resources or smart pointers. With smart pointers you either are using ref counting or single owner semantics. Single owner is very limited, it doesn't allow you to build shared structures where there are multiple ways of reaching a value. If you're using ref counting it's not too bad, though you can't ever build cyclic data structures and you have to keep track of who is holding onto references or you get space & resource leaks. This last point is true of GC'ed languages too, forgetting that some object holds a reference to some resource causes space leaks.

In my experience, essentially all the times where we have to be concerned with use of a precious resource there are very clear bounds where the resource is needed. In almost all of these cases simple block scoping of the resource is sufficient. As you say there are some cases where we want more flexible techniques like pools but again these can be implemented in any language GC'd or not.

Sure the Java example where it does not let you do any manual resource management and only closes files via a finaliser is silly. All it needs is a .close() method or something and then in a nice functional language with cheap abstractions you don't even need to remember to call .close() as (like in my earlier example) you can write functions which encapsulate the resource usage patterns like allocate, use, release.

What, again?

Posted May 2, 2007 18:26 UTC (Wed) by bronson (subscriber, #4806) [Link]

> if your object is allocated on the stack (which Java forbids)...

If you didn't notice, I'm using Java finalizers and its lack of automatic objects as an example of broken design.

> ...this does get you a kind of "auto-destruction" semantics. But is this a common case?

In C++, absolutely. It couldn't be more common.

I feel like I've answered your question but you're refusing to acknowledge it. Just because Java blew it with finalizers, that doesn't mean that destructors are broken too. Did you understand the second example in my post? I'm happy to explain further if you'd like.

Copyright © 2008, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds