Using Interfaces for Exception Safety in Delphi
Thursday, 20 September 2007
Resource Management and Exception Safety
One concept that has become increasingly important when writing C++ code is that of Exception Safety — writing code so that invariants are maintained even if an exception is thrown. Since exceptions are also supported in Delphi, it makes sense to apply many of the same techniques to Delphi code.
One of the important aspects of exception safety is resource management — ensuring that resources are correctly freed even in the presence of exceptions, in order to avoid memory leaks or leaking of other more expensive resources such as file handles or database connection handles. Probably the most common resource management idiom in C++ is Resource Acquisition is Initialization (RAII). As you may guess from the name, this involves acquiring resources in the constructor of an object. However, the important part is that the resource is released in the destructor. In C++, objects created on the stack are automatically destroyed when they go out of scope, so this idiom works well — if an exception is thrown, then the local objects are destroyed (and thus the resources they own are released) as part of stack unwinding.
In Delphi, things are not quite so straight-forward: variables of class type are not stack allocated, and must be explicitly
constructed by calling the constructor, and explicitly destroyed — in this respect, they are very like raw pointers in
C++. This commonly leads to lots of try
-finally
blocks to ensure that variables are correctly destroyed
when they go out of scope.
Delphi Interfaces
However, there is one type of Delphi variable that is automatically destroyed when it goes out of scope — an
interface
variable. Delphi interfaces behave very much like reference-counted pointers (such as
boost::shared_ptr
) in this regard — largely because they are used to support COM, which requires this
behaviour. When an object is assigned to an interface variable, the reference count is increased by one. When the interface variable
goes out of scope, or is assigned a new value, the reference count is decreased by one, and the object is destroyed when the
reference count reaches zero. So, if you declare an interface
for your class and use that interface type exclusively,
then you can avoid all these try
-finally
blocks. Consider:
type abc = class constructor Create; destructor Destroy; override; procedure do_stuff; end; procedure other_stuff; ... procedure foo; var x,y: abc; begin x := abc.Create; try y := abc.Create; try x.do_stuff; other_stuff; y.do_stuff; finally y.Free; end; finally x.Free; end; end;
All that try
-finally
machinery can seriously impact the readability of the code, and is easy to
forget. Compare it with:
type Idef = interface procedure do_stuff; end; def = class(TInterfacedObject, Idef) constructor Create; destructor Destroy; override; procedure do_stuff; end; procedure other_stuff; ... procedure foo; var x,y: Idef; begin x := def.Create; y := def.Create; x.do_stuff; other_stuff; y.do_stuff; end;
Isn't the interface
-based version easier to read? Not only that, but in many cases you no longer have to worry about
lifetime issues of objects returned from functions — the compiler takes care of ensuring that the reference count is kept
up-to-date and the object is destroyed when it is no longer used. Of course, you still need to make sure that the code behaves
appropriately in the case of exceptions, but this little tool can go a long way towards that.
Further Benefits
Not only do you get the benefit of automatic destruction when you use an interface to manage the lifetime of your class object,
but you also get further benefits in the form of code isolation. The class definition can be moved into the
implementation
section of a unit, so that other code that uses this unit isn't exposed to the implementation details in
terms of private methods and data. Not only that, but if the private data is of a type not exposed in the interface
,
you might be able to move a unit from the uses
clause of the interface
section to the
implementation
section. The reduced dependencies can lead to shorter compile times.
Another property of using an interface is that you can now provide alternative implementations of this interface. This can be of great benefit when testing, since it allows you to substitute a dummy test-only implementation when testing other code that uses this interface. In particular, you can write test implementations that return fixed values, or record the method calls made and their parameters.
Downsides
The most obvious downside is the increased typing required for the interface definition — all the public properties and methods of the class have to be duplicated in the interface. This isn't a lot of typing, except for really big classes, but it does mean that there are two places to update if the method signatures change or a new method is added. In the majority of cases, I think this is outweighed by the benefit of isolation and separation of concerns achieved by using the interface.
Another downside is the requirement to derive from TInterfacedObject
. Whilst you can implement the
IInterface
methods yourself, unless you have good reason to then it is strongly recommended to inherit from
TInterfacedObject
. One such "good reason" is that the class in question already inherits from another class, which
doesn't derive from TInterfacedObject
. In this case, you have no choice but to implement the functions yourself, which
is tedious. One possibility is to create a data member rather than inherit from the problematic class, but that doesn't always make
sense — you have to decide for yourself in each case. Sometimes the benefits of using an interface are not worth the
effort.
As Sidu Ponnappa does in his post 'Programming to interfaces' strikes again, "programming to interfaces" doesn't mean to create an interface for every class, which does seem to be what I am proposing here. Whilst I agree with the idea, I think the benefits of using interfaces outweigh the downsides in many cases, for the reasons outlined above.
A Valuable Tool in the Toolbox
Whilst this is certainly not applicable in all cases, I have found it a useful tool when writing Delphi code, and will continue to use it where it helps simplify code. Though this article has focused on the exception safety aspect of using interfaces, I find the testing aspects particularly compelling when writing DUnit tests.
Posted by Anthony Williams
[/ delphi /] permanent link
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
1 Comment
I'm new to Delphi and was wondering how to write exception safe code (or good code at all) without RAII or GC. The interface trick sounds really interesting (the first thing i like about the Delphi interface thing). Thank you for this great article!