notes-computer-programming-programmingLanguageDesign-prosAndCons-destructors

"

A technical point: you can achieve destructor-style resource management in Go like so:


interface Disposable { Dispose() }

func using(r Disposable, f func()) { defer r.Dispose(); f(); }

example conn := db.NewConnection?(connString); using(conn, func() { ... conn ... });


Thanks to the interface, this could be done generically as well. It also illustrates another powerful feature of Go, anonymous closures, that C++ is only just getting.

Rivorus (December 13, 2009 12:40 PM) [2-part again]

"Your claim was that defer interfered with generic programming. I demonstrated that that was false, assuming everyone adopted a certain convention."

You did nothing of the sort. You showed exactly what I was saying, you have to jump through hoops and be explicit every single time you create an object with nontrivial disposal when in actuality you should get the correct behavior implicitly by default and with no chance of error. The language is Turing complete, no one is saying that you can't reach the same end goal for a given application both with and without destrutors, only that it is needlessly more difficult without them and more error prone.

"I don't know why he thinks you'd have to wait for a GC to release the queues' resources. Dispose() would do it immediately."

He didn't say that Dispose wouldn't do it, he said that if you as the programmer do not explicitly Dispose then you get resources released at a noneterministic time after the object leaves scope (finalization). This simply cannot happen when you use destructors since the correct behavior happens automatically.

"He writes the Java version of Transfer incorrectly. In a correct version, the commenter's point about exceptions thrown by Dispose would not apply. (You'd nest the try blocks.)"

He is writing the simplest code whose end result is comparable to the corresponding C++/CLI code that he posted to illustrate his point about having to be explicit. He could have nested try blocks but it would have just made the code even more complicated than it already is. If you want to really be pedantic, you are also running into the fundamental differences between how exceptions are dealt with in C++ and in Java -- in C++, exceptions thrown but never caught in the destructor do not propagate, they cause a terminate, meaning that if you really wanted to make the code as closely comparable to C++ as possible, not only would you need all those try/cathes, but you'd be terminating rather than propagating if a dispose let an exception leak through. All of this is beyond the point of his post but only further argues in favor of the approach with destructors. Again, destructors are automatically called whereas "defer" or the dispose pattern of Java and C# require you to be explicit every time a type with non-trivial disposal is instantiated.

"But we should consider how often they are applicable. Definitely for file I/O, since the most common pattern is to read or write completely. Not at all for locks, because locks are always longer-lived than the scope in which you lock them; so in C++ you need a wrapper class for RAII, and the code is about the same as you'd get with "using". Sometimes for network connections; but often you want to hold on to a connection for a while before tearing it down, and you may want to free it asynchronously, say after it has been idle for a while. You end up with some sort of connection pool, and then I don't think the code looks very different in C++ vs. C#. "

All of those cases are perfect examples of where destructors are applicable, and in fact I use RAII in those situations all of the time and would certainly not trade it for "defer". In the hypothetical cases you are talking about, for instance where locks are unlocked after the locking object goes out of scope (which I don't know why you claim is always the case, it's actually almost never the case), you are exhibiting times where you either declare the lock object at one scope and lock later on, or, less likely, you want shared ownership of the lock object. With C++ you are able to very easily and automatically handle both cases and still get deterministic unlocking immediately after all of the objects end their lifetime with virtually no chance of error. Further, I'm not sure what you are even trying to point out here since you are providing no example of a better approach with "defer".

.. Keep in mind I never said "defer" wasn't useful, only that destructors are much better suited for almost all common cases and are automatic rather than explicit and error prone. The times you'd want to use a "defer" like feature over destructors are when you need to do something unique to that particular scope. That does happen and it is why "defer" is very useful. The two features are in no way mutually exclusive.

"As for Go, I don't see it adopting destructors, because that would lead down the path of constructors, assignment operators and the like, and the Go designers are very clear that they don't want to go down that route. It's plausible that Go will adopt GC finalizers, but beyond that I think we should not expect any help from the language." "

Rivorus (December 13, 2009 12:44 PM) To Julian:

"I really don't see the advantage in immediate deallocation of memory. "free" isn't free. You are shuffling memory in itty bitty slices, when a compacting GC could clear and release it in bulk."

We are not specifically talking about freeing memory, we are talking about releasing important resources, closing files, closing connections, releasing locks, etc.

"If you need to access a resource which needs cleanup through an interface, perhaps you should have a "Release()" method as part of the interface, which you can defer."

Again, that is explicit and incorrect by default if not used. Destructors are automatic and correct.

Here is a better example since you say:

"... who knows what the next library version will bring"

The situation with destructors is both automatic and much safer with respect to future development. Imagine that type "A" has trivial disposal so the programmer decides to have no "defer" statement when it is instantiated. Later on in development -- weeks, months, or years -- the type "A" grows and implementation details change to where it now should close down resources. All of a sudden there should be a deferred disposal when you instantiate "A".

So what happens to that previous code that was written? Since "defer" wasn't used, you now have to go back and insert it everywhere, otherwise nothing is properly cleaned up.

The way around this horrible scenario would be to have made the original type "A" have an empty "dispose" method and the places that instantiate it have a "defer" statement that calls the empty dispose, postulating that sometime in the future "A" may evolve to have nontrivial disposal. If you abide by this pattern you now you have an explicit "defer" everywhere you instantiate a scope-bound instance of your type even though its dispose currently does nothing.

What then is the benefit of that over having it automatic? If every time you instantiate your type you should be defering disposal, you lose absolutely nothing by combining the operations implicitly. You have now made the type impossible to misuse.

This does not even cover places with shared ownership, where disposal happens deterministically after it has no more owners. You still have to do extra management -- management which is done automatically in C++ because of RAII with respect to smart pointers. Doing this with only a feature like "defer" is far from feasible.

    A technical point: you can achieve destructor-style resource management in Go like so:
    ------
    interface Disposable { Dispose() }
    func using(r Disposable, f func()) {
    defer r.Dispose();
    f();
    }
    // example
    conn := db.NewConnection(connString);
    using(conn, func() {
    ... conn ...
    });
    --------
    Thanks to the interface, this could be done generically as well. It also illustrates another powerful feature of Go, anonymous closures, that C++ is only just getting.
    Rivorus (December 12, 2009 6:07 PM) "I've little doubt that you could find a way to parse XML into C++ structs using template metaprogramming, but I don't think it would be pretty, and of course the I/O time would dominate anyway, so it wouldn't be measurably more efficient.
    Very true, however with the Boost.Spirit library in C++ you can do it very nicely and it's surprisingly intuitive as well. Of course, you don't want to look at all of the library code that makes it possible unless you are really into metaprogramming as you have pointed out.
    "A technical point: you can achieve destructor-style resource management in Go like so:"
    That's actually exactly what I am talking about. What you posting is exactly the type of thing that destructors accomplish implicitly and with less code. As well, with just defer the "incorrect" solution happens by default if you forget. The code you posted is analogous to the C# or Java code mentioned in the post by Herb Sutter that I linked to. Everything he states when talking about Java and C# there applies to Go with respect to defer, minus the part about exceptions since Go doesn't [currently] have exceptions.
    Jonathan Amsterdam (December 13, 2009 8:57 AM) First, let's keep hold of the thread of the argument. Your claim was that defer interfered with generic programming. I demonstrated that that was false, assuming everyone adopted a certain convention. (You could now argue that Go has done nothing to foster such a convention, and I would agree with you.)
    Now let's talk about destructors vs. using for resource management:
    Some minor points about Sutter's post: He writes the Java version of Transfer incorrectly. In a correct version, the commenter's point about exceptions thrown by Dispose would not apply. (You'd nest the try blocks.) And I don't know why he thinks you'd have to wait for a GC to release the queues' resources. Dispose() would do it immediately.
    But I agree with Sutter's main point: destructors are better when applicable. Most of my day-to-day programming is in C++, and I love this aspect of the language. But we should consider how often they are applicable. Definitely for file I/O, since the most common pattern is to read or write completely. Not at all for locks, because locks are always longer-lived than the scope in which you lock them; so in C++ you need a wrapper class for RAII, and the code is about the same as you'd get with "using". Sometimes for network connections; but often you want to hold on to a connection for a while before tearing it down, and you may want to free it asynchronously, say after it has been idle for a while. You end up with some sort of connection pool, and then I don't think the code looks very different in C++ vs. C#.
    It's worth pointing out that garbage-collected languages have the disadvantage that there is no delete, and typical garbage collectors may run at some arbitrarily distant point in time after a resource is released. I think Limbo had a great solution to that: it used reference counting for non-cyclic structures, and guaranteed that they would be freed immediately upon becoming unreferenced. As a practical matter, resource-holding objects are almost never cyclic, so the idea should work well (though I have no practical experience with it).
    As for Go, I don't see it adopting destructors, because that would lead down the path of constructors, assignment operators and the like, and the Go designers are very clear that they don't want to go down that route. It's plausible that Go will adopt GC finalizers, but beyond that I think we should not expect any help from the language.
    Julian Morrison (December 13, 2009 11:37 AM) I really don't see the advantage in immediate deallocation of memory. "free" isn't free. You are shuffling memory in itty bitty slices, when a compacting GC could clear and release it in bulk.
    If you need to access a resource which needs cleanup through an interface, perhaps you should have a "Release()" method as part of the interface, which you can defer.

Jonathan, I think you miss the point about the limits of "defer". In R.'s ABCD example, if something in C calls for defer code, there's no way for the person coding A to know that, so it can't be written. A destructor is the only place where it could be put.

Rivorus (December 13, 2009 12:40 PM) [2-part again]

"Your claim was that defer interfered with generic programming. I demonstrated that that was false, assuming everyone adopted a certain convention."

You did nothing of the sort. You showed exactly what I was saying, you have to jump through hoops and be explicit every single time you create an object with nontrivial disposal when in actuality you should get the correct behavior implicitly by default and with no chance of error. The language is Turing complete, no one is saying that you can't reach the same end goal for a given application both with and without destrutors, only that it is needlessly more difficult without them and more error prone.

"I don't know why he thinks you'd have to wait for a GC to release the queues' resources. Dispose() would do it immediately."

He didn't say that Dispose wouldn't do it, he said that if you as the programmer do not explicitly Dispose then you get resources released at a noneterministic time after the object leaves scope (finalization). This simply cannot happen when you use destructors since the correct behavior happens automatically.

"He writes the Java version of Transfer incorrectly. In a correct version, the commenter's point about exceptions thrown by Dispose would not apply. (You'd nest the try blocks.)"

He is writing the simplest code whose end result is comparable to the corresponding C++/CLI code that he posted to illustrate his point about having to be explicit. He could have nested try blocks but it would have just made the code even more complicated than it already is. If you want to really be pedantic, you are also running into the fundamental differences between how exceptions are dealt with in C++ and in Java -- in C++, exceptions thrown but never caught in the destructor do not propagate, they cause a terminate, meaning that if you really wanted to make the code as closely comparable to C++ as possible, not only would you need all those try/cathes, but you'd be terminating rather than propagating if a dispose let an exception leak through. All of this is beyond the point of his post but only further argues in favor of the approach with destructors. Again, destructors are automatically called whereas "defer" or the dispose pattern of Java and C# require you to be explicit every time a type with non-trivial disposal is instantiated.

"But we should consider how often they are applicable. Definitely for file I/O, since the most common pattern is to read or write completely. Not at all for locks, because locks are always longer-lived than the scope in which you lock them; so in C++ you need a wrapper class for RAII, and the code is about the same as you'd get with "using". Sometimes for network connections; but often you want to hold on to a connection for a while before tearing it down, and you may want to free it asynchronously, say after it has been idle for a while. You end up with some sort of connection pool, and then I don't think the code looks very different in C++ vs. C#. "

All of those cases are perfect examples of where destructors are applicable, and in fact I use RAII in those situations all of the time and would certainly not trade it for "defer". In the hypothetical cases you are talking about, for instance where locks are unlocked after the locking object goes out of scope (which I don't know why you claim is always the case, it's actually almost never the case), you are exhibiting times where you either declare the lock object at one scope and lock later on, or, less likely, you want shared ownership of the lock object. With C++ you are able to very easily and automatically handle both cases and still get deterministic unlocking immediately after all of the objects end their lifetime with virtually no chance of error. Further, I'm not sure what you are even trying to point out here since you are providing no example of a better approach with "defer".

Anonymous (June 6, 2011 7:30 PM) "x = NewX?(); defer x.Destroy()" is an idiom - the defer immediately following the instantiation. Anyone who's read some example code would be surprised to see no defer. It would be a "loud silence".

Oh, so it's an "idiom" that every object is destroyed when the function that allocated it returns? Do people who write such things actually program for a living? Shudder. I sure hope I'm not using any of their code. The same goes for Jonathan Amsterdam, who commits the even worse sin of disposing of an object allocated in the caller -- and manually, yet; how quickly he forgets that the whole point of defer is to guarantee that the object is destroyed when it will no longer be used. Of course, these examples together make the point -- the only way to get it right in all cases is with dtors (RAII).

Also, Jonathan's code example highlights a fatal weakness of Go's interfaces -- they aren't composable. Suppose I want to dispose of r after passing it to a function that expects a Foobler -- how do I declare r to be both Disposable and a Foobler? I can't ... and I never will be able to, because composability would contradict Go's simple(-minded) design of no type hierarchies, and the type syntax is designed in a rigid way that makes it awkward to add such features.


finalizers in python:

http://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python/

" Ouch… what has happened? Where are the destructors? Here’s what the Python documentation has to say on the matter:

    Circular references which are garbage are detected when the option cycle detector is enabled (it’s on by default), but can only be cleaned up if there are no Python-level __del__() methods involved.

Python doesn’t know the order in which it’s safe to destroy objects that hold circular references to each other, so as a design decision, it just doesn’t call the destructors for such methods!

"

" Jean-Paul CalderoneNo? Gravatar Says: June 12th, 2009 at 14:28

What you’re calling “destructors” here are actually “finalizers”. Python also has destructors, but it’s rare that custom destructors are required (and they can only be defined in C).

Also, the main problem with your argument is that it is very difficult to ensure that an object doesn’t become a part of a cycle after some new development or maintenance is done to the library of which it is part.

How do you write a unit test which verifies an object isn’t part of any cycle in your program?

If you can’t write such a unit test, how do you gain confidence that your finalizers will continue to be called?

Marius GedminasNo? Gravatar Says: June 12th, 2009 at 15:59

I believe there are more downsides people usually mention about Python’s finalizers:

– they behave differently in different Python implementations (Jython, IronPython?, PyPy?), so their execution may be delayed

– they may not necessarily get called before the program exits

– weird things happen if you do something in your __del__ that causes the object to get resurrected by creating another reference to it

Personally, I think __del__ methods are okay-ish for a last-ditch defense, but should not be relied on in code. Always free your resources explicitly by calling the appropriate close() method. mike bayerNo Gravatar Says: June 12th, 2009 at 17:32

__del__ is inadequate for more reasons than one. Your solution of never building any cycles between objects is difficult to ensure – and mistakenly adding non-weakref’ed cycles adds hard to track memory leaks. Additionally, traversing the objects along weakrefs as opposed to direct references introduces palpable function call overhead in a performance critical application. You can’t even count on the state of “self” within __del__ – the state of the object is in undefined, some attributes may be there or may not be there depending on random circumstances.

The better way IMO is to just use the weakref() alone with a callback as the finalizer – the weakref can even be from the object to itself (just make sure the callback doesn’t reference self). when the callback is called, the original referent is gone, so there’s no chance that you might “accidentally” call some state off of self that won’t always be there. you then don’t need to worry about cycles causing surprise memory leaks.

http://docs.python.org/reference/datamodel.html#object.__del__

 Richard JonesNo Gravatar Says:June 13th, 2009 at 08:28

__del__ can be called during program exit – thus the state of your program is completely undefined (for example, modules may have been collected & cleaned out). It’s not broken, you just have to be very, very careful when using __del__. elibenNo Gravatar Says: June 13th, 2009 at 10:11

@Richard,

Do you mean to say that an object can have its instance vars cleaned up before its __del__ method is called? Wouldn’t this mean that __del__ is useless by definition? Lennart RegebroNo? Gravatar Says: June 14th, 2009 at 09:08

Summarum: __del__ is not guaranteed to get called. At all. It’s not only for cyclical references. Therefore it can’t be used to do thinks as deallocate things that Python does not automatically deallocate anyway.

The calling order of __del__ is not guaranteed. Therefore you can not use it for locking resources.

It’s also tricky to use, but that’s another thing, Trickyness is not the problem.

Summa Summarum: If it can’t be used for resource allocation, and it can’t be used for locking, then what can it be used for? Yeah, I don’t know. So why does it exist? Beats me. ChadNo? Gravatar Says: June 14th, 2009 at 22:24

We avoid all use of __del__ in the IMVU client for two reasons:

1) It’s very difficult to ensure the no-cycles requirement, especially with common use of closures and continuous refactoring.

2) I’ve witnessed situations in Python 2.5 where a cycle of objects without __del__ methods references a leaf object with a __del__ method and the leaf object prevents the cycle from being collected. Unfortunately, I can’t reproduce this in a test, so I suspect it’s an interpreter bug.

weakref callbacks on leaf resources and explicit destruction have worked better for us. Imre TuskeNo? Gravatar Says: November 13th, 2010 at 21:07

This is an issue I faced when I started to learn Python (and came from a C++ background). At the end I also found weakref as something useful, but during my investigations in python finalizers (which are not destuctors, indeed), I frowned upon the realization that your finalizers are not even guaranteed to be called.

Beats me, too. Unfortunately, the conclusion is that for the time being you can’t properly implement C++ RAII ctor/dtor symmetry in Python. Too bad. :(