Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi XE7 smart pointers

I am new to Delphi with a C++ background and trying to figure out how smart pointers can be implemented. I came across the following post, which I am trying to use as my own starting point: Delphi - smart pointers and generics TList

However I can't compile the previous code using Delphi XE7 (compiler errors are shown as comments in code). Also I would greatly appreciate it if someone actually explained the code's logic (initially I wanted to use the class as a drop in utility class but now I would like to understand what is actually happening). I vaguely understand that because the smart pointer implementation is inheriting from TInterfacedObject, it is reference counted but anything beyond that makes no sense to me :)

unit SmartPointer;

interface

uses
  SysUtils, System.Generics.Collections;

type
  ISmartPointer<T> = reference to function: T;

  // complains ISmartPointer<T> expecting an interface type
  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;

// complains: overload procedure TSmartPointer.Create must be marked with the overload directive
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.

Tried to use the previous smart pointer with following test code which resulted in a compiler error…what am I missing?

program TestSmartPointer;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, SmartPointer;

type
TPerson = class
  private
    _name : string;
    _age : integer;
  public

    property Name: string read _name write _name;
    property Age: integer read _age write _age;
  end;

var
  pperson : TSmartPointer<TPerson>;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    pperson := TSmartPointer<TPerson>.Create();
    // error on next line: undeclared Identifier: Name
    pperson.Name := 'John Doe';
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
like image 562
BigONotation Avatar asked Mar 31 '15 20:03

BigONotation


1 Answers

You have to declare your reference variable as ISmartPointer<TPerson>:

var
  pperson : ISmartPointer<TPerson>;

The following code would also compile, but it would not automatically release memory in this case, because when you store a reference-counted object instance into a object reference you mess up its reference-counting mechanism. Depending on the code, it can result in memory leaks or premature destruction of the underlying object instance.

var
  pperson : TSmartPointer<TPerson>;

begin
  pperson := TSmartPointer<TPerson>.Create();
  pperson.Invoke.Name := 'John Doe';

Finally the following code illustrates the correct smart pointer usage:

var
  pperson : ISmartPointer<TPerson>;   // note pperson is ISmartPointer<TPerson>

begin
  pperson := TSmartPointer<TPerson>.Create();
  pperson.Name := 'John Doe';

Some interface basics

An interface defines a contract - functionality that the class implementing the interface must have, without providing a specific implementation. The IFoo interface declaration means when you have reference to IFoo, you can call Foo method on that reference, but that is all you can.

IFoo = interface
  procedure Foo;
end;

When a class implements an interface, it must implement all methods from that interface. Method Foo from IFoo will map to method Foo from TFoo or TOtherFoo. Implementation of specific interfaces can be different in different classes.

TFoo = class(TInterfacedObject, IFoo)
public
  procedure Foo;
  procedure Bar;
end;

TOtherFoo = class(TInterfacedObject, IFoo)
public
  procedure Foo;
end;

procedure TFoo.Bar;
begin
  writeln('Bar');
end;

procedure TFoo.Foo;
begin
  writeln('Foo');
end;

procedure TOtherFoo.Foo;
begin
  writeln('Other Foo');
end;

var
  foo: IFoo;
  f: TFoo;

  foo := TFoo.Create;
  foo.Foo; // Output -> Foo

  // Compiler error -> foo is interface reference and only knows Foo from TFoo
  foo.Bar;

  foo := TOtherFoo.Create;
  foo.Foo; // Output -> Other Foo

  // Mixing object reference with reference counted object instance -> memory leaks
  f := TFoo.Create;
  foo.Foo; // output -> Foo
  foo.Bar; // f is TFoo object reference, and it knows everything from TFoo

How a smart pointer actually works

ISmartPointer<T> is declared as anonymous function.

ISmartPointer<T> = reference to function: T;

Above declaration is equivalent of interface with Invoke function

ISmartPointer<T> = interface
  function Invoke: T;
end;

The difference between the two (the one we are interested here) is that with anonymous function/methods you don't have to explicitly call Invoke; the compiler will do that for you.

Since ISmartPointer<T> is an anonymous function, which is actually an interface in declaration of TSmartPointer<T> class, Invoke method will be mapped to ISmartPointer<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;

var
  pperson : ISmartPointer<TPerson>;

So when you write pperson.Name behind the curtains that translates to a pperson.Invoke function call which returns a TPerson instance from FValue and TPerson has the Name property that the compiler can recognize.

Since TSmartPointer<T> is a reference-counted class, when you use ISmartPointer<T> references, the underlying TSmartPointer<T> object instance, together with the T instance it contains in FValue will be automatically released when the ISmartPointer<T> reference goes out of scope, or you set it to nil in code.

like image 193
Dalija Prasnikar Avatar answered Oct 01 '22 14:10

Dalija Prasnikar