|
|
Subscribe / Log in / New account

A little OO goes a long way

A little OO goes a long way

Posted May 3, 2013 9:15 UTC (Fri) by NAR (subscriber, #1313)
In reply to: A little OO goes a long way by jwakely
Parent article: Go and Rust — objects without class

In many other languages you can use something like try-finally to achieve this.


to post comments

A little OO goes a long way

Posted May 3, 2013 11:10 UTC (Fri) by hummassa (subscriber, #307) [Link] (8 responses)

when you have some resource external to your program represented by some type (a file, a windows, a proxy to a variable in another machine), you'll *always* want to take steps to destroy a variable of this type. So, for each variable of this type, you'll be typing (and others will be reading, and it will inflation the SLOCcount of your code) "try \n ... finally \n ... ... \n end" for no good reason, and without adding to the intended semantics, and exposing yourself as the programmer to an error introduced by a typo, the forgetting of some condition, or even to the optimization of some condition that was impossible but suddently become possible (e. g., when you wrote the code you knew you were closing some file in every possible path but some maintenance creates a new code path, like some new exception coming from a new version of a library, where the file goes out of scope still open).

A little OO goes a long way

Posted May 3, 2013 13:00 UTC (Fri) by jwakely (subscriber, #60262) [Link] (7 responses)

Also, you'll be writing the same code everywhere you want to clean up a resource of that type. Why repeat yourself, why not associate the cleanup code with the type and make it run automatically?

I'm baffled why any self-respecting programmer would want to duplicate all that cleanup logic in every finally block or why they'd question the advantage of destructors.

Python's with statement is the right idea, the object's scope is limited and its type has some predefined cleanup code that runs automatically when the scope ends. Bingo.

A little OO goes a long way

Posted May 3, 2013 14:18 UTC (Fri) by ehiggs (subscriber, #90713) [Link] (1 responses)

To bring it back on topic, Go has 'defer'[1] which is a bit of try-finally and with/destructors by putting the deferred call near the construction site.

It's still not as good as Python's 'with' in my opinion because you have to be explicit about the cleanup call in Go and in Python it's not cluttering up the code (aside from the indent level).

[1] http://golang.org/doc/effective_go.html#defer

A little OO goes a long way

Posted May 4, 2013 5:37 UTC (Sat) by danieldk (subscriber, #27876) [Link]

And it's by for not as good as destructors:

- It puts the burden on the programmer and not the type.
- defer only works when leaving the scope of the function calling it. A type's destructors are called when going out of any scope in C++, e.g. when an instance of a class is used as a (non-pointer) member variable.

A little OO goes a long way

Posted May 3, 2013 15:53 UTC (Fri) by hummassa (subscriber, #307) [Link] (4 responses)

Oh, the duplication is the least of the problems. A bigger one would be the combination of optimization by the programmer and external changes. So, the following code:
try {
  f = open(...)
  f.x()
  f.y()
  if( z ) f.writeCheckSumAndLastBuffer() else f.writeLastBuffer()
} finally {
  f.close()
}
generates a hidden bug when f.y(), that calls a w() function from an external library, starts seen some exception and the last buffer is not written. Bonus points if the "if(z)" thing was put by another programming, in the run of the normal maintenance of the program.

A little OO goes a long way

Posted May 4, 2013 5:45 UTC (Sat) by danieldk (subscriber, #27876) [Link] (3 responses)

I don't see how that is a problem of try/finally. Consider this C++
{
   Resource someResource(...);
   resource.x();
   resource.y(); // throws
   resource.writeLastBuffer();
}

If y() throws here in C++, writeLastBuffer() is never called either. I if you always want to write something, add it to close() or ~Resource().

Also, Java 7 has a nicer try-with-resources statement, like Python's with, for classes that implement AutoClosable:

try (Resource r = new Resource(...)) {
  // Do something with r...
}

A little OO goes a long way

Posted May 4, 2013 10:21 UTC (Sat) by hummassa (subscriber, #307) [Link]

It still burdens the "client" programmer on remembering to use the new try thingy. Destructors have zero client programmer overhead.

A little OO goes a long way

Posted May 4, 2013 10:26 UTC (Sat) by hummassa (subscriber, #307) [Link]

> I don't see how that is a problem of try/finally.

You are right, of course. But using destructors you have a lot more chances that discovering that some code belong in an destructor and putting it there because in the client code the WriteLastBuffer thing sticks out like a sore thumb. :-D

Ah, and once you wrote it, all call sites are correct from now on.

A little OO goes a long way

Posted May 7, 2013 14:52 UTC (Tue) by IkeTo (subscriber, #2122) [Link]

Consider this C++
{
   Resource someResource(...);
   resource.x();
   resource.y(); // throws
   resource.writeLastBuffer();
}

C++ programmers are accustomed to a concept called RAII, Resource Acquisition is Initialization. So if they always want the last buffer written, they tend to write something like:

class LastBufferWriter {
public:
  LastBufferWriter(Resource resource): resource_(resource) {}
  ~LastBufferWriter() { resource_.writeLastBuffer(); }
private:
  Resource resource_;
};

... {
   Resource resource(...);
   LastBufferWriter writer(resource);
   // Anything below can throw or not throw, we don't care
   resource.x();
   resource.y();
}

Not to say that everybody like having to define a class for every cleanup, though. But with C++0x lambda expression, the above can easily be automated.

A little OO goes a long way

Posted May 3, 2013 11:39 UTC (Fri) by jwakely (subscriber, #60262) [Link]

Yes, I know. That's an inferior solution compared to destructors.

A little OO goes a long way

Posted May 3, 2013 12:52 UTC (Fri) by rleigh (guest, #14622) [Link]

try..finally is a very poor alternative. Running the destructor when the object goes out of scope or is deleted gives you strict, deterministic cleanup. Using try..finally means I have to reimplement the same logic, *by hand*, everywhere in the codebase where the object goes out of scope. And if I forget to do this in just one place, I'm now leaking resources. What's the chance that this will happen in a codebase of any appreciable size, especially allowing for changes as a result of ongoing maintenance and refactoring? It is effectively guaranteed.

The really great thing about this being done in the destructor is that I can be satisfied that I will never leak resources by default, ever. It's simply not possible. This is the real beauty of RAII; cleanup just happens under all circumstances, including unwinding by exceptions.

As a relatively recent newcomer to Java from a C++ background, I have to say I find the resource management awful, and this stems directly from its lack of deterministic destruction. While it might do a decent job of managing memory, every other resource requires great care to manage by hand, be it file handles, locks or whatever, and I've seen several serious incidents as a result, typically running out of file handles. And the enforcement of checking all thrown exceptions itself introduces many bugs--you can't just let it cleanly and automatically unwind the stack, thereby defeating one of the primary purposes of having exceptions in the first place--decoupling the throwing and handling of errors.

By way of comparison, I haven't had a single resource leak in the C++ program I maintain in 8 years, through effective use of RAII for all resources (memory, filehandles, locks).

Regards,
Roger


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