How would you "extract" nested try/finally blocks from a routine into a reusable entity? Say I have
procedure DoSomething;
var
Resource1: TSomeKindOfHandleOrReference1;
Resource2: TSomeKindOfHandleOrReference2;
Resource3: TSomeKindOfHandleOrReference3;
begin
AcquireResource1;
try
AcquireResource2;
try
AcquireResource3;
try
// Use the resources
finally
ReleaseResource3;
end;
finally
ReleaseResource2;
end;
finally
ReleaseResource1;
end;
end;
and want something like
TDoSomething = record // or class
strict private
Resource1: TSomeKindOfHandleOrReference1;
Resource2: TSomeKindOfHandleOrReference2;
Resource3: TSomeKindOfHandleOrReference3;
public
procedure Init; // or constructor
procedure Done; // or destructor
procedure UseResources;
end;
procedure DoSomething;
var
Context: TDoSomething;
begin
Context.Init;
try
Context.UseResources;
finally
Context.Done;
end;
end;
I want this to have the same exception-safety as the nested original. Is it enough to zero-initialize the ResourceN variables in TDoSomething.Init
and do some if Assigned(ResourceN) then
checks in TDoSomething.Done
?
Yes, you can use a single try/finally/end block for multiple resources with zero-initialization.
Another possible solution can be found in Barry Kelly blog
There are three things about classes that make this idiom safe and easy:
Free
on a null reference, so you never need to checkAssigned
first.Since the destructor can rely on all fields to have known values, it can safely call
Free
on everything, regardless of how far the constructor got before crashing. Each field will either hold a valid object reference or it will be nil, and either way, it's safe to free it.Use it the same way you use any other class:
The pattern with testing on Assigned in finally is used in the Delphi source. You do kind of the same thing but I think you should move Context.Init to capture exception from Context.Init.
Edit 1 This is how you should do it without Context.Init and Context.Done. If you place all AquireResource code before
try
you will not free Resource1 if you get an exception in AcquireResource2