The constructor/destructor pair is an incredibly powerful concept!Well, you can't have everything: either we support garbage collection or destructors, isn't it? But it just one more point where Java sucks: the ugly try/finally block and then the explicit close() call like in the following code:
BufferedReader reader =Ugly? You bet! And what I really don't like is that you cannot hide all the required handling in a library! In C++ you'd just write:
new BufferedReader(new FileReader(aFileName));
try {
String line = null;
while ( (line = reader.readLine()) != null ) {
// process the line...
}
}
finally {
reader.close();
}
ifstream f(aFileName);That's all, the plumbing is hidden in the destructor* and it is there automatically! It's the reason why Stefan called this concept an "incredibly powerful" one. But that powerful concept can cause problems in a multithreading and garbage-collected environment. As a matter of fact, a recent C++ standard proposal for the multithreading execution model opted for removing destructors from the language (!!!), or at least for not executing the static destructors in a multithreading setting! Of course, it's a shortcut in order to solve a rather complicated problem, but you get the idea, right?
string line;
while(f)
{
getline(f, line);
// process the line...
}
So maybe the destructors are a little bit outdated, what do you think Stefan? All the more was I pleased as I recently stumbled on a Smalltalk pattern called "Execute Around Method" pattern** (to be true, I don't do any Smalltalk and have seen it in some Groovy example code). It's another possiblity to hide the plumbing in the library: you just define a static method doing all the dirty work and accepting your "payload" code - just like in an IP packet: we have the framing and the payload, and the user is only delivering the payload! Well, an example explains it best:
def static use(closure)The closure parameter stands for our "payload" code. This code is the hidden in the library. An application of this is the following Groovy code:
{
def r = new Resource()
try
{
r.open()
closure(r)
}
finally
{
r.close()
}
}
new FileReader(aFileName).withReaderWe create a new reader, give it to the static withReader() library method, and provide a "code block" (as you'd call it in Perl) for execution. This code block (called closure in Groovy) gets as the parameter the ressource which will be closed at the end, just like the use() method shown above!
{ reader ->
reader.readLine(line)
// process the line...
}
// no need to close()!!!
A destructor for the modern times! So the "incredibly powerful" idea can be saved?
---
* this is called a RAII-pattern in C++, see: http://www.hackcraft.net/raii/
** Kent Beck: "Smalltalk Best Practice Patterns", Prentice Hall, Englewood Cliffs, NJ, 1996.
I'm about 1.5 years in the future. If you ever read this, can you go into more detail about why destructors can cause problems in a multithreading and garbage-collected environment?
ReplyDeleteIn a multithreading environment, I think the problem of when to destroy an object is simply a matter of either (1) reference counting or (2) terminating all threads that access an object in the reverse of the order they were created (and then destroying the shared object last).
In a garbage-collected environment, I think there should be a distinction between destruction of managed (memory) and unmanaged (files, network connections, ...) resources . .NET, for instance, makes this distinction with its IDisposable pattern, although it doesn't do destruction as simply as C++, in C# at least.
@Anonymous: sorry for the late answer, but I was pretty tied-up and didn't any blogging at all recently :-(.
ReplyDeleteThe problem with destructors which can cause problems in a multithreading environment was at that time, that the C++ committee considered to abolish calling of destructors of static objects in threads (if my memory is serving me righ, I tried to find the original statement and #failed). This was then dropped I think? Somehow I did assume that without checking, maybe I should have a look at this.
...and garbage-collected environment?
Well that's obvious: as Java's case shows, you can either have the GC or destructors. You know, the finalizers are an utter debacle! In C# the IDisposable pattern is making this a little more bearable, but it must be invoked by hand, so thet's not a true destructor.
Well, that's what I think, but as that's 1,5 later, I can be mistaken, so everybody - correct me if I'm wrong!