The Pragmatic Programmer: From Journeyman to Master

Balancing and Exceptions

Languages that support exceptions can make resource deallocation tricky. If an exception is thrown, how do you guarantee that everything allocated prior to the exception is tidied up? The answer depends to some extent on the language.

Balancing Resources with C++ Exceptions

C++ supports a trycatch exception mechanism. Unfortunately, this means that there are always at least two possible paths when exiting a routine that catches and then rethrows an exception:

void doSomething( void ) { Node *n = new Node; try { // do something } catch (...) { delete n; throw; } delete n; }

Notice that the node we create is freed in two placesonce in the routine's normal exit path , and once in the exception handler. This is an obvious violation of the DRY principle and a maintenance problem waiting to happen.

However, we can use the semantics of C++ to our advantage. Local objects are automatically destroyed on exiting from their enclosing block. This gives us a couple of options. If the circumstances permit, we can change "n" from a pointer to an actual Node object on the stack:

void doSomething1( void ) { Node n; try { // do something } catch (...) { throw; } }

Here we rely on C++ to handle the destruction of the Node object automatically, whether an exception is thrown or not.

If the switch from a pointer is not possible, the same effect can be achieved by wrapping the resource (in this case, a Node pointer) within another class.

// Wrapper class for Node resources class NodeResource { Node *n; public: NodeResource() { n = new Node; } ~NodeResource() { delete n; } Node * operator ->() { return n; } }; void doSomething2( void ) { NodeResource n; try { // do something } catch (...) { throw; } }

Now the wrapper class, NodeResource, ensures that when its objects are destroyed the corresponding nodes are also destroyed. For convenience, the wrapper provides a dereferencing operator ->, so that its users can access the fields in the contained Node object directly.

Because this technique is so useful, the standard C++ library provides the template class auto_ptr, which gives you automatic wrappers for dynamically allocated objects.

void doSomething3( void ) { auto_ptr<Node> p (new Node); // Access the Node as p->... // Node automatically deleted at end }

Balancing Resources in Java

Unlike C++, Java implements a lazy form of automatic object destruction. Unreferenced objects are considered to be candidates for garbage collection, and their finalize method will get called should garbage collection ever claim them. While a convenience for developers, who no longer get the blame for most memory leaks, it makes it difficult to implement resource clean-up using the C++ scheme. Fortunately, the designers of the Java language thoughtfully added a language feature to compensate, the finally clause. When a try block contains a finally clause, code in that clause is guaranteed to be executed if any statement in the try block is executed. It doesn't matter whether an exception is thrown (or even if the code in the try block executes a return )the code in the finally clause will get run. This means we can balance our resource usage with code such as

public void doSomething() throws IOException { File tmpFile = new File(tmpFileName); FileWriter tmp = new FileWriter(tmpFile); try { // do some work } finally { tmpFile.delete(); } }

The routine uses a temporary file, which we want to delete, regardless of how the routine exits. The finally block allows us to express this concisely.

Категории