As I'm sure most developers knows, when allocating any type of resource, recommended Delphi coding practice is to free the resource within a Try Finally construct, as in:
o := TStringList.Create;
Try
// Work with o
Finally
o.free;
End;
The point here being that if any code between the Try and Finally fails, you are guaranteed to free your object. In fact, this is such a common coding practice that a code template exists explicitly for this. Enter the word Try, press Ctrl-J to invoke code templates, select Try/Finally (create/free) from the pull down list and you'll see the editor inserting this type of code fragment for you.
Object Allocation Before or After the Try?
One frequently asked question is whether you should place the object creation inside the Try, or before the Try, as I did here. Borland's code template places it before the try, and that's the "officially" correct place. Let's take a look at why this is recommended.
The answer has to do with the fact that the constructor itself may fail. That is, the call to TStringList.Create may fail. With the call to TStringList.Create before the Try, if this call does indeed fail, the code inside the Finally block will not execute - and that's what we want. If the constructor failed, Delphi does not allocate any memory for the object, the assignment to to the object is not made, and if we had proceeded to attempt to release that memory our call to Free would fail. That is, if we had written the code as:
Try
o := TStringList.Create;
// Work with o
Finally
o.free;
End;
And the call to TStringList.Create failed, our call to Free, located within the Finally block, would still execute, generating its own exception. So the recommended place to place your object creation is before the try, in case the constructor fails. Now let's look at the case where you need to sequentially allocate multiple resources, and we may change our minds&
Allocating more than one Object - the "correct" way
Now consider the case when you instantiate a series of objects. I'll demonstrate the point with just two objects, but as you'll see the problem is worse if you need more than two objects. The "official" solution is to instantiate each object before its own Try / Finally construct, as in:
o1 := TStringList.Create;
Try
o2 := TStringList.Create;
Try
// Use o1 and o2 here
Finally
o2.free;
Finally
o1.Free;
End;
Work through the possibilities of where code could fail and you'll see this code is 100% failsafe. Wherever any error could conceivably occur, this code will correctly clean up. But the code is awkward, and the more objects you have to create and release the more awkward this solution becomes. An easier solution is to place all the object creations inside the try, exactly the opposite of the recommended approach we just covered.
Allocating more than one object - the "easier" way
The key to this approach is to understand what Free actually does. No, it does not release the memory occupied by the object - that's done by the destroy method, which we never call directly (do we?). Free simply checks whether the object which called it is Nil. If it is Nil, it does nothing. If it is not nil, it proceeds to call Destroy. You could imagine Free being written as:
Procedure TObject.Free;
Begin
If Self <> Nil Then
Destroy;
End;
So if Free does not destroy the object if it is nil, we can solve the sequential resource allocation problem by rewriting our code as:
o1 := Nil;
o2 := Nil;
Try
o1 := TStringList.Create;
o2 := TStringList.Create;
// Use o1 and o2 here
Finally
o2.free;
o1.Free;
End;
We now have the instantiation inside the Try, but note how we explicitly set the object pointers to nil outside the Try. This way, if either of the instantiations fails, although the call to free is still made it is not a problem since the object is nil, and we know that free will not do anything if the object is nil. And note that you must explicitly set the object to nil - local variables are not nil by default.
|