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&lt;TPerson&gt;);
  // TPerson param
  procedure ShowAge(APerson: TPerson);

var
  Person1: ISmartPointer&lt;TPerson&gt;;
  Person2: ISmartPointer&lt;TPerson&gt;;
  Person3: ISmartPointer&lt;TPerson&gt;;
  PersonObj: TPerson;
begin
  // Typical usage when creating a new object to manage
  Person1 := TSmartPointer&lt;TPerson&gt;.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&lt;TPerson&gt;.Create(PersonObj);
  ShowName(Person2);
  // Note: PersonObj is freed by the smart pointer

  // Smart pointer constructs the TPerson instance
  Person3 := TSmartPointer&lt;TPerson&gt;.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.

2 Responses

  1. 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;
    
  2. I do have a different solution. Usage is:

    procedure Test;
    var
      lStrings: TStrings;
    begin
      TObjectGuard.Guard(lStrings, TStringList.Create);
      lStrings.Foo;
    end; // lStrings is freed as soon as method is left as TObjectGuard.Guard returns an IInterface
    

    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.