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