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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With