Barry Kelly recently posted an example of “smart” pointers (specifically the auto-pointer variant of a smart pointer) using generics in Delphi 2009. It was an interesting use of generics but the end result was something that has – in part at least – been possible for some time in Delphi even without generics – reliable cleanup of objects.
This was something that a colleague of mine, Geza Sabo, pointed out, based on some code I’d previously shared with him to robustly manage the hourglass cursor in a GUI application.
Barry’s – and others – generics based approach aims (I think) to provide a generally useful mechanism for declaring some object reference that will automatically be free’d when it goes out of scope or is part of a container that itself is disposed of, for example in class member variable (field) declarations.
In practice I think – day-to-day – that the most useful effect of smart pointers will be the removal of the boiler-plate code that is otherwise needed to ensure correct clean-up of temporary objects in method implementations:
var list: TStringList; begin list := TStringList.Create; try // Do work with list finally FreeAndNIL(list); end; end;
This boiler-plate can become particularly unattractive when multiple temporary objects are involved. With the generics solution presented by Barry, the need for the boiler-plate is entirely removed:
var list: TSmartPointer<TStringList>; begin list := TStringList.Create; // Do work with list end;
Unfortunately there are some problems with the implementation (mostly problems with side effects of the debugger) discussed in the comments of Barry’s post.
The biggest problem (for me) is that it only works in Delphi 2009, but for the temporary objects case at least, there is an alternative that will work even in older Delphi versions. I personally am also uncomfortable with expressing behaviour in a declaration, but that is a foible of mine that others may not necessarily share.
How It Works
The underlying mechanism that makes the generics approach possible is referencing counting on an interfaced object, a mechanism which of course is not itself dependent on generics at all and we can use the exact same mechanism:
interface type PObject = ^TObject; function AutoFree(const aObject: PObject): IUnknown; implementation uses SysUtils; type TAutoFree = class(TInterfacedObject, IUnknown) private fObject: PObject; public constructor Create(const aObject: PObject); destructor Destroy; override; end; constructor TAutoFree.Create(const aObject: PObject); begin inherited Create; fObject := aObject; end; destructor TAutoFree.Destroy; begin FreeAndNIL(fObject^); inherited; end; { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } function AutoFree(const aObject: PObject): IUnknown; begin result := TAutoFree.Create(aObject); end;
In use, this implementation is very similar to the generics based approach (for temporary objects):
var list: TStringList; begin list := TStringList.Create; AutoFree(@list); // Do work with list end;
There are three fairly obvious differences and one perhaps not so obvious:
1. The variable is declared as what it is, not as how it should behave
2. There is still some boiler-plate (the call to AutoFree())
3. The use of the @ operator
The first difference is important from a usage and maintenance point of view.
In terms of usage we continue to use our variables as what they are; wrapping them inside a smart pointer makes this more cumbersome in some cases.
From a maintenance point of view it allows us to change the behaviour of our code (in cases where the changes would not otherwise affect the variables required) without having to change variable declarations. It also means that changes to behaviour (in terms of changes to the body of our methods) cannot inadvertently break the behaviour established in the declaration (which in practice may be some distance removed from the behavioural code being modified).
The second difference actually follows from the first. Since behaviour is not now introduced in the declaration it is now necessary to specify that behaviour in the code. However, the impact on that code is still far less obtrusive than the usual try..finally boiler-plate. It is also more flexible, thanks to the third and final (obvious) difference.
This third difference is the use of the the @ operator in the calls to AutoFree(). Initially this might appear cumbersome – why not just pass the object reference to be free’d directly? The answer to that brings me to the perhaps not-so-obvious difference.
By using references to object references, where we specify the behaviour is not actually important. It also means that the behaviour is robust where the objects involved are perhaps only conditionally created and/or sometimes also explicitly free’d:
var listA: TStringList; listB: TStringList; begin AutoFree(@listA); AutoFree(@listB); listA := TStringList.Create; listB := NIL; // We do some work with listA if someCondition then begin listB := TStringList.Create; // Do work involving listB (and maybe listA too) end; end;
In the behavioural aspect of our code (the method body) we stipulate – via a call to AutoFree() – that when we exit the method, any reference in the specified variable should be freed. It doesn’t actually matter at the point at which we make that call whether that variable is currently NIL, uninitialised(*) or already contains a valid object reference. What will matter is what that reference contains when the method exits(**).
(*) it will matter if the variable is never initialised (see problem #2, below).
(**) even if the method raises an exception the AutoFree() references will still be reliably cleaned-up.
Of course, this approach itself isn’t perfect.
1. As previously mentioned, AutoFree() deals specifically with temporary objects and does not address more general uses of “smart” pointers.
2. Unfortunately for a reason I can’t quite fathom, a call to AutoFree(@ref) seems to be sufficient for the compiler to consider ref to be initialised, so you will not get a “Variable ‘ref’ might not have been initialized” warning if you call AutoRef() for some variable but then neglect to initialise that variable.
3. An interfaced object is a lot of overhead for each reference being AutoFree()d – of course this can easily be addressed by an overload to accept an array of PObject references and extending the TAutoFree class to manage more than one reference per instance.
Hourglass Management
As I mentioned at the start, the initial implementation of AutoFree() was inspired by a more specific implementation that utilised the same reference counting mechanism to reliably manage the hourglass cursor in GUI applications.
But I’m saving that for next time.
🙂
You know, I have to tell you, I really enjoy this blog and the insight from everyone who participates. I find it to be refreshing and very informative. I wish there were more blogs like it. Anyway, I felt it was about time I posted, I
The solution you present here and the “smart pointer” wrapper I presented serve two different purposes; the former serves for stack-frame-bounded scope, while the latter can extend to arbitrary object graph usage.
Also, the solution you present depends on interface return value surviving to the end of the current procedure. However, this behaviour may change in the future. For one thing, it is inefficient. And the behaviour is already different if the method happens to be inlined somewhere (the lifetime gets extended, though, rather than curtailed).
The smart pointer wrapper I presented has another problem, though; it doesn’t provide any way to lift operators, so working with such smart pointers is tedious, as you have to constantly dereference with the ‘.Value’ accessor. This situation may improve at some unspecified point in the future, as it would also be useful to properly implement nullable types.
@Barry – thanks for the comments. I did mention the different ambitions of the two approaches, although I tried to use language that I think most people would understand. Perhaps it got lost in translation?
🙂
The lifetime of the returned interface is a good point, and you are in a better position than I to say whether it will actually change although the theoretical possibility that it might has to be acknowledged.
I would be very surprised if it did ever change though, as there is surely a very real likelihood that it might break the behaviours of code that perhaps inadvertently depend on this behaviour, as well as code that does so deliberately – however inadvisedly.
If not wishing to break legacy code is sufficient reason for there to exist two ANSIPos() routines (one of which takes UnicodeString, not ANSIString), surely such a fundamental change in lifetime behaviours would be utterly beyond the pale?
😉
Having said that, and not-with-standing my previously stated personal preference for not expressing behaviour in declarations, it strikes me that the desired behaviours could be reliably and unobtrusively incorporated via direct language support:
someObj: TFoo automated;
You could even claim some “green coding” credentials by recycling that “automated” keyword. It’s not a perfect keyword but is close enough I think (it’s an “auto pointer”, after all).
🙂
Underneath, the compiler would produce whatever code is required to “Free” someObj, depending on what someObj is.
for a member (field) there could be a list of such references that every object destroys when it too is destroyed (it would be important for the “automated” references to be NIL’d as well as Free’d, of course)
for a local variable the compiler would add clean-up code to the stack frame (did I use that term correctly? ianace – “ce” == compiler engineer).
for a unit variable the compiler would add clean-up code to the unit finalization
Maybe?
Of course, such language support would also fall foul of the “not supported by earlier versions of Delphi test”.
Hey-ho.
🙂
Very informative blog … as usual !
And regarding the “automated” idea … it is just brilliant and it will make sense for native development to avoid boiler plate code (up to a time when a real GCed memory manager will emerge in Delphi) !
@Barry
Regarding boiler plate, would you envision a more modern/mixed (ex: Java like) try .. catch.. finally support in next Delphi ?
This might be just some syntactic sugar (like new D2009 Exit(params) )
Thanks for the credits 😉
> someObj: TFoo automated;
Er, am I the only person seeing this as equivalent to bringing back stack-based objects? You know, like things were in Object Pascal before Delphi took them out?
Owen, it’s not the same thing at all.
An “automated” reference, as proposed, would not create an object at all.
It would be a decoration on an object reference (or equally I suppose some pointer type) variable indicating that we wish the RTL to dispose – at the appropriate time – any object (or memory) referenced by that variable.
For object references that would be “inject a call to .Free using this reference”. For pointers: FreeMem() I suppose.
It’s really no different from the behaviour already in place for interface references (the compiler injects calls to .Release()). The difference is that this code is ALWAYS injected for interface references, but for object references/pointers the developer needs to decide which ones need to be disposed.
This is what the “automated” decoration would achieve.
Also, strictly speaking Delphi didn’t “take out” stack-based objects. They were deprecated but they still essentially worked as long as you avoided known problem areas.
I work today with well established code that incorporates stack-based objects that have charted a reliable course thru to Delphi 5 and beyond.
> Owen, it’s not the same thing at all.
I think that’s overstating it. For object references, the big difference is that you control when the object is created. But the big similarity is that they get cleaned up for you when they go out of scope.
I want to use RAII cleanly in Delphi. With stack-based objects you mostly get it, but since variables have to be declared in a var section you wouldn’t get the same freedom you do applying RAII in some other languages.
Arguably though, RAII is a workaround for the lack of an “automated” keyword :-).
(Granted, applying “automated” to pointers generally extends the usefulness of the keyword somewhat.)
Fair point about Delphi still allowing stack-based objects — but the caveats make that an option you wouldn’t want to use unless you needed to, IMHO.
There is also a huge difference in the clean-up. Stack based objects (in Delphi at least) just “disappear” when they “fall off” the stack – there is no destructor as such, at least I don’t think there is/was. I’ve certainly not seen them used in those few examples of stack-objects I’ve encountered.
I should perhaps mention that for obvious reasons it would be essential for an automated reference to be initialised to NIL.
It’s all very similar to the way interface references are already currently implemented!
Wouldn’t it be appropriate if the “automated” keyword – rendered obsolete at the same time that interfaces were incorporated in Delphi – could find a new lease of life inspired – in part at least – by interfaces themselves?
🙂
> Stack based objects (in Delphi at least) just “disappear” when they “fall off” the stack[…]
What? Surely not … [checks] … Ah. Oh dear. That’s what happens when you talk about something you haven’t used for ten years.
[Wipes egg off face]
In my dotage, I appear to have confused my memory of old-style classes with C++ classes. D’oh.
My whole point was predicated on the idea that old-style objects DO have normal constructors and destructors, and ARE cleaned up when they go out of scope. That’s how RAII works. No wonder you didn’t see the similarity.
Argument retracted.
If you are not concerned about conditionally created and/or sometimes also explicitly free’d objects, you can eliminate the need for the @ operator:
function AutoFree(var aObject): IUnknown;
begin
result := TAutoFree.Create(@aObject);
end;
And if you are not concerned merging the object creation and behaviour definition into one line:
function AutoFree(var aObject; aNewObject:TObject): IUnknown;
begin
TObject(aObject) := aNewObject;
result := TAutoFree.Create(@aObject);
end;
procedure Foo;
var list: TList;
begin
AutoFree(list,{:=} TList.Create);
…
@Owen – no worries. I wasn’t entirely sure myself and also had to check.
🙂
@Geza – yes, you could do that.
In the first instance however you could now inadvertently try to “AutoFree” something that is not a TObject!
In the second case the construction of your objects becomes deeply intertwined with what is otherwise an entirely separate statement of intended behaviour.
If you wish to add (or remove) AutoFree()ing behaviour to an object at some point over the life of the code you have to directly modify the statement that creates it, rather than it being expressed as the separate concern that it actually is.
Very interesting and useful your post ( and many others … thanks !! )
I implemented an overloaded version that accept an array of PObject :
var
..
begin
AutoFree( [@obj1 , @obj2 , @obj3 ]);
..
I think was very useful this Autofree to avoid nested try / finally …
( see http://stackoverflow.com/questions/398137/what-is-the-best-way-to-do-nested-try-and-finally-statement-in-delphi ).
… but i do not understand the real advantage on a simple procedure ( no interface overhead ) that free all objects :
procedure FreeAll( const aObjects: array of TObject );
var
…
begin
…
FreeAll( [obj1 , obj2 , obj3 ]);
end;
… ops … sorry … 🙂 … because i need try / finally!!
var
…
begin
try
…
finally
FreeAll( [obj1 , obj2 , obj3 ]);
end;
end;