Introduction
Today, I had a discussion on StackOverflow (I hope no one deleted it yet) on the usefulness of smart pointers instead of try
...finally
.
My arguments are as follows:
1. try
...finally
decreases the readability of a function, especially if they are nested, e.g. when you instantiate a TStream
and then a TStringList
. Then you would need two nested try
...finally
constructs, thoroughly decreasing readability.
2. The code that destroys (frees) the object can be several lines removed from the place where it is created. Smart pointers allow you to take care of lifetime management right where you create the object.
3. Even if you are very good at manual memory management, taking care of it is still a chore, and it is always nice to have something, like ARC, or smart pointers, that can take that away from you. It greatly reduces the number of situations where you can make mistakes.
4. No matter how good you are, you can always forget to call Free
, or call LongNamedObjectA.Free
when you actually meant LongNamedObjectAB.Free
. Such errors are hard to find, especially if they are several lines away from the actual creation spot. Using smartpointers, you don't have to repeat yourself this way, so you can't make such mistakes.
So if people tell you that only lazy or incompetent people would use smart pointers, or that people won't be able to understand your code if you use them, don't believe it. They greatly reduce the chance to make mistakes, even if you are competent at manual memory and lifetime management. They remove a lot of clutter and make your code more readable and let you concentrate on what you want to do, not on lifetime management anymore.
Implementation
I have seen quite a few smart pointer implementations. The nice thing about them is that they take care of automatically destroying the "guarded" object, so you don't have to take care of that yourself. So instead of something like:
var X: TMyObject; begin X := TMyObject.Create(17); try // Several lines of code using X finally X.Free; end; end;
Now should be able to do something like:
var X: TMyObject; begin X := SmartPointer<TMyObject>(TMyObject.Create(17)); // Several lines of code using X end;
And at the end of the function, X is freed automatically.
Awkward to use
Unlike what I want to have, most implementations I have seen require you to declare the smart pointers as extra variables:
var X: TMyObject; SP: SmartPointer<TMyObject>; begin X := SmartPointer<TMyObject>(SP, TMyObject.Create(17)); // Several lines of code using X end;
This is so for the ObjectHandle
by Barry Kelly, as well as the ISafeGuard
types in the JCL. But I never liked that. It makes you repeat yourself needlessly and makes the code less readable than it could be, taking away one of the advantages smart pointers provide, in my opinion.
Trick
So I employ a few tricks. The first one is that if a function or constructor returns a record, and this record is never assigned to a variable, the compiler still puts an anonymous record on the stack. That is because, for any record that is larger than a register, a record that is returned is actually passed as a var parameter. So the following:
function Bla(I, J: Integer): TMyRecord;
is actually compiled as:
procedure Bla(I, J: Integer; var Result: TMyRecord);
But that means that an actual record must be passed. If, in your code that uses the function, you don't assign the result of the function, the compiler creates an anonymous record on the stack and passes that, so inside your function you can assign values to the fields of Result
.
Note that the use of the anonymous record mechanism is safe. It is also employed for intermediate results of class operators like + or *. If I use my BigIntegers
, or Delphi's sample TComplex
, then D := A + B * C;
actually produces the code AnonymousIntermediate := op_Multiply(B, C); D := op_Add(A, AnonymousIntermediate);
It will not change anytime soon.
Here comes the second trick: I want to return a TMyObject
, and not a smart pointer record, so I can assign the result directly to X
. For this, I (ab)use the Implicit
operators you can define for a record. I define a
class operator Implicit(A: TMyObject): SmartPointer<TMyObject>;and a
class operator Implicit(A: SmartPointer<TMyObject>): TMyObject;So if I do the following:
X := SmartPointer<TMyObject>(TMyObject.Create(17));
then first, the first Implicit
operator returns a record (a smart pointer). This is an anonymous record. But since X
is a TMyObject
, subsequently the other Implicit
operator converts the anonymous record into a TMyObject
again. How this "conversion" (actually just some passing around of a reference) is done can be seen in the code a bit further on.
So now I don't have to explicitly declare any smart pointers and can return and assign the created object in one fell swoop. The only thing to take care of is making this generic. That is what I have done in the following simple unit.
Many years ago, I implemented the original smart pointers in the JCL and called them Guards
, and I want to remain faithful to that name. But since they are a little "smarter" and easier to use then those early implementations (which did not have overloaded operators or records with methods yet), I called them SmartGuards
instead.
The unit
unit Velthuis.SmartGuards; interface type IGuard = interface ['{CE522D5D-41DE-4C6F-BC84-912C2AEF66B3}'] end; TGuard = class(TInterfacedObject, IGuard) private FObject: TObject; public constructor Create(AObject: TObject); destructor Destroy; override; end; SmartGuard<T: class> = record private FGuard: IGuard; FGuardedObject: T; public class operator Implicit(GuardedObject: T): SmartGuard<T>; class operator Implicit(Guard: SmartGuard<T>): T; end; implementation { TGuard } constructor TGuard.Create(AObject: TObject); begin FObject := AObject; end; destructor TGuard.Destroy; begin FObject.Free; inherited; end; { SmartGuard} class operator SmartGuard<T>.Implicit(GuardedObject: T): SmartGuard<T>; begin Result.FGuard := TGuard.Create(GuardedObject); Result.FGuardedObject := GuardedObject; end; class operator SmartGuard<T>.Implicit(Guard: SmartGuard<T>): T; begin Result := Guard.FGuardedObject; end; end.
Sample code
Here is a simple (braindead?) program that uses them. It defines a simple but talkative class that shows when it is being destroyed, and which has a name. The demo shows how it can be used with the SmartGuard
.
program SmartGuardDemo; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Velthuis.SmartGuards in 'Velthuis.SmartGuards.pas'; type TTalker = class private FName: string; public constructor Create(const Name: string); destructor Destroy; override; procedure Talk; end; { TTalker } constructor TTalker.Create(const Name: string); begin FName := Name; end; destructor TTalker.Destroy; begin Writeln(FName, ' is being destroyed'); inherited; end; procedure TTalker.Talk; begin Writeln(FName, ' is talking'); end; procedure Test; var A, B: TTalker; I: Integer; begin Writeln('Creating object London...'); A := SmartGuard<TTalker>(TTalker.Create('London')); Writeln('Creating object Paris...'); B := SmartGuard<TTalker>(TTalker.Create('Paris')); A.Talk; B.Talk; Writeln('OK, so they talked'); for I := 1 to 100 do Write(I:8); Writeln; Writeln; Writeln('Leaving the test'); end; begin Test; Readln; end.
Conclusion
I am sure that these smart pointers could do with some enhancements, and that there could be smart pointers for plain pointers/memory blocks too, or smart pointers that can be stored in lists, arrays, etc. and keep their guarded objects alive for longer than a function. This is just the base frame I have been thinking of.
As was said in the StackOverflow discussion, smart pointers are not a known Delphi idiom. I think that is a pity. But that would require a standard implementation (or a number of them) in the Delphi runtime library. Perhaps one day, we will see them there. Or they will be superseded by ARC. Who knows?
Rudy Velthuis