Manual memory management requires some thought and ceremony. It is not so much of a problem with long-lasting instances, but managing temporary local objects, especially when you need to create more than one, is a tedious job. It also hurts code readability.
While Delphi allows automatic memory management for classes that implement interfaces, using ARC is not always feasible. If you have to deal with preexisting classes that don't support ARC, you will have to deal with manual memory management. Reference counting also adds some overhead, and using reference counting in your own classes is not always a viable approach.
When performance is not paramount, simpler and cleaner code without
try...finally blocks would be nice to have. Life always finds a way and
various ARC-based smart pointer and similar lifetime management wrapper
implementations have started to appear in the wild.
One common place where constructing many object instances can be found is unit testing. While lifetime wrappers work well in applications, they often wrap object instances too well, and that can create issues in unit testing.
There is another approach to lifetime management that does not require wrapping the managed object - using some kind of auto-release pool that will take ownership (and memory management) over object instances, while keeping the original reference intact. In the case of single objects, there is no need for a pool and maintaining the list.
Implementing such auto-release managers is rather simple. Managers on their own
are reference-counted: Their memory is automatically managed, and will be
released when they go out of scope. To ensure proper construction and reference
counting initialization for the
TAutoReleasePool classes, their
declaration will be hidden in the implementation section of a unit, and they can be
constructed only through the
AutoRelease record's static functions.
TAutoRelease class can manage the lifetime of a single object instance. Since
the compiler maintains a hidden interface reference in the method scope, there is no
need for declaring an additional variable.
procedure TestXXX.TestYYY; var Foo: TFoo; begin Foo := TFoo.Create; AutoRelease.New(Foo); ... // use Foo end; // The TAutoRelease object will be destroyed at this point, and will release Foo
TAutoReleasePool class can manage the lifetime of multiple object instances. To
use a pool, we need to keep a reference to the pool. In a unit testing environment,
the pool can be a field in the test class that will be created and destroyed for every
test in the
TestXXX = class(TTestCase) strict private AutoPool: IAutoReleasePool; ... procedure TestXXX.SetUp; begin inherited; AutoPool := AutoRelease.NewPool; end; procedure TestXXX.TearDown; begin AutoPool := nil; inherited; end; procedure TestXXX.TestYYY; var Foo: TFoo; Bar: TBar; begin Foo := TFoo.Create; AutoPool.Add(Foo); Bar := TBar.Create; AutoPool.Add(Bar); ... // use Foo and Bar end;
Using such auto-release managers is not limited to a unit testing environment, and
they can be used in regular code instead of full lifetime management wrappers.
They do require an extra line of code compared to smart pointer implementations,
but they still simplify code and make
try...finally blocks redundant.
And here is a full implementation of auto-release managers:
interface type IAutoReleasePool = interface procedure Clear; procedure Add(aInstance: TObject); end; AutoRelease = record public class function New(aInstance: TObject): IInterface; static; class function NewPool: IAutoReleasePool; static; end; implementation type TAutoRelease = class(TInterfacedObject) strict private fInstance: TObject; public constructor Create(aInstance: TObject); destructor Destroy; override; end; TAutoReleasePool = class(TInterfacedObject, IAutoReleasePool) strict private fInstances: TList<TObject>; public constructor Create; destructor Destroy; override; procedure Clear; procedure Add(aInstance: TObject); end; constructor TAutoRelease.Create(aInstance: TObject); begin fInstance := aInstance; end; destructor TAutoRelease.Destroy; begin fInstance.Free; inherited; end; constructor TAutoReleasePool.Create; begin fInstances := TObjectList<TObject>.Create; end; destructor TAutoReleasePool.Destroy; begin fInstances.Free; inherited; end; procedure TAutoReleasePool.Clear; begin fInstances.Clear; end; procedure TAutoReleasePool.Add(aInstance: TObject); begin fInstances.Add(aInstance); end; class function AutoRelease.New(aInstance: TObject): IInterface; begin Result := TAutoRelease.Create(aInstance); end; class function AutoRelease.NewPool: IAutoReleasePool; begin Result := TAutoReleasePool.Create; end;