Smart pointers have a few uses, the most common being for memory management, providing a way of automatically managing the lifetime of an object in non garbage collected languages. In Delphi this is similar to what you get with reference counted interfaces but a smart pointer implementation does this in a generic way such that all objects can be managed without having to change the existing class (no need to implement interfaces or descend from TInterfacedObject).
Guru compiler engineer Barry Kelly blogged about smart pointer implementations back in late 2008:
Smart pointers in Delphi
Reference-counted pointers, revisited
Somewhat more efficient smart pointers
Barry’s final implementation is a marvel in generics, anonymous methods and interfaces all rolled into one simple and effective class. Without the ability to override the member pointer/access operator in Delphi previous smart pointer implementations required accessing the managed object through a getter of the smart pointer class or by using records and overriding the implicit conversion operator. By using anonymous methods (which are implemented using interfaces) the getter is no longer required, making usage more intuitive and with cleaner code.
Smart Pointer Implementation
You have to read between the lines a bit in Barry’s posts to extract the final class and usage so I took the relevant pieces and put them together as follows. I’ve renamed the definitions, added the ability to call the managed object’s default or parameterless constructor automatically, and created a slightly more elaborate example showing how to use it.
unit SmartPointer; interface uses SysUtils; type ISmartPointer<T> = reference to function: T; TSmartPointer<T: class, constructor> = class(TInterfacedObject, ISmartPointer<T>) private FValue: T; public constructor Create; overload; constructor Create(AValue: T); overload; destructor Destroy; override; function Invoke: T; end; implementation { TSmartPointer<T> } constructor TSmartPointer<T>.Create; begin inherited; FValue := T.Create; end; constructor TSmartPointer<T>.Create(AValue: T); begin inherited Create; if AValue = nil then FValue := T.Create else FValue := AValue; end; destructor TSmartPointer<T>.Destroy; begin FValue.Free; inherited; end; function TSmartPointer<T>.Invoke: T; begin Result := FValue; end; end.
ISmartPointer<T> simply provides a more meaningful name to the existing TFunc anonymous method declaration. TSmartPointer<T> implements the anonymous method interface which provides reference counting and anonymous method behaviour. Overriding the Invoke function gives direct member access to the managed object and the constructor and destructor take ownership of the object and free it when the smart pointer interface reference is no longer used.
Example Usage
TPerson = class public constructor Create(const AName: string; const AAge: Integer); reintroduce; procedure Birthday; // Increment Age property Name: string ... property Age: integer ... end; // Smart pointer param procedure ShowName(APerson: ISmartPointer<TPerson>); // TPerson param procedure ShowAge(APerson: TPerson); var Person1: ISmartPointer<TPerson>; Person2: ISmartPointer<TPerson>; Person3: ISmartPointer<TPerson>; PersonObj: TPerson; begin // Typical usage when creating a new object to manage Person1 := TSmartPointer<TPerson>.Create(TPerson.Create('Fred', 100)); Person1.Birthday; // Direct member access! ShowName(Person1); // Pass as smart pointer ShowAge(Person1); // Pass as the managed object! //Person1 := nil; // Release early // Same as above but hand over to smart pointer later PersonObj := TPerson.Create('Wilma', 90); // Later Person2 := TSmartPointer<TPerson>.Create(PersonObj); ShowName(Person2); // Note: PersonObj is freed by the smart pointer // Smart pointer constructs the TPerson instance Person3 := TSmartPointer<TPerson>.Create(); // or Create(nil) // The smart pointer references are released in reverse declaration order // (Person3, Person2, Person1) end;
Smart pointers are especially convenient when creating short-lived objects created and freed within a single method and avoids the need for complex try finally blocks.
Adding a record and you can get rid of the extra create. Only disadvantage you cannot combine the power of records operator overloads and the implicit Invoke call of the interface. But you can combine them:
I do have a different solution. Usage is:
The types TOnDestroy and TDestroyProc are not necessary and purely optional.
I also have a descending class (which I also do attach) which only frees the object on leaving the method in case there’s an exception (which frees me from using try/except in functions returning objects in case of a failure). Using these classes I also have been able to write a simple threadsafe Logger which can log the Name of a method when entering and leaving it with just a single command as “Logger.LogEnterMethod;” (which than uses MAP Files and inspection of the callstack to get the name of the current method). Leaving the method will be logged without any furher code.