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:
type SmartPointer<T: class, constructor> = record strict private FPointer: ISmartPointer<T>; public class operator Implicit(Value: T): SmartPointer<T>; class operator Implicit(const Value: SmartPointer<T>): ISmartPointer<T>; end; class operator SmartPointer<T>.Implicit(Value: T): SmartPointer<T>; begin Result.FPointer := TSmartPointer<T>.Create(Value); end; class operator SmartPointer<T>.Implicit(const Value: SmartPointer<T>): ISmartPointer<T>; begin Result := Value.FPointer; end; var s: SmartPointer<TPerson>; i: ISmartPointer<TPerson>; begin s := TPerson.Create; i := s; i.Age := 32; end;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.
unit Core.Classes.ObjectGuard; interface type TOnDestroy = procedure(AObject: TObject) of object; TDestroyProc = reference to procedure(AObject: TObject); TObjectGuard = class(TInterfacedObject, IInterface) private FOnDestroy: TOnDestroy; FDestroyProc: TDestroyProc; constructor Create(AObject: TObject; AOnDestroy: TOnDestroy; ADestroyProc: TDestroyProc); protected FObject: TObject; public procedure BeforeDestruction; override; destructor Destroy; override; class function Guard(var AInstance; AObject: TObject; AOnDestroy: TOnDestroy = nil): IInterface; overload; class function Guard(var AInstance; AObject: TObject; ADestroyProc: TDestroyProc): IInterface; overload; end; implementation uses SysUtils; { TObjectGuard } //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: class function TObjectGuard.Guard(var AInstance; AObject: TObject; AOnDestroy: TOnDestroy): IInterface; begin Assert(Assigned(AObject)); TObject(AInstance) := AObject; Result := Create(AObject, AOnDestroy, nil); end; //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: class function TObjectGuard.Guard(var AInstance; AObject: TObject; ADestroyProc: TDestroyProc): IInterface; begin Assert(Assigned(AObject)); TObject(AInstance) := AObject; Result := Create(AObject, nil, ADestroyProc); end; //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: constructor TObjectGuard.Create(AObject: TObject; AOnDestroy: TOnDestroy; ADestroyProc: TDestroyProc); begin Assert(Assigned(AObject)); inherited Create; FObject := AObject; FOnDestroy := AOnDestroy; FDestroyProc := ADestroyProc; end; //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: procedure TObjectGuard.BeforeDestruction; begin inherited; if Assigned(FOnDestroy) then FOnDestroy(FObject); if Assigned(FDestroyProc) then FDestroyProc(FObject); end; //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: destructor TObjectGuard.Destroy; begin FreeAndNil(FObject); inherited; end; end. unit Core.Classes.ExceptGuard; interface uses Core.Classes.ObjectGuard; type TExceptGuard = class(TObjectGuard) public destructor Destroy; override; end; implementation { TExceptGuard } //---------------------------------------------------------------------------- //2011-07-11 00:00 N,SM: destructor TExceptGuard.Destroy; begin if (ExceptObject = nil) then FObject := nil; inherited; end; end.