Generally speaking Pascal allows for managed types, those are types that execute special code on Initialization (when the data is created), Finalization (when it is destroyed), Copy (when it is assigned) and Referencing (when you use it as a function parameter).
A type is managed if either:
1. It's a long string or a dynamic array
2. It's a COM interface
3. It's a record having management operators
4. It's a composite type that contains other managed types (e.g. a record containing a string will perform the management operations for the string inside).
Strings, Dynamic Arrays and COM interfaces are reference counted, meaning on Initialization the reference count is 1, on copy or reference it's incremented and finalization it is decremented and once it hits 0, the data is freed.
For management operators you can simply implement your own logic:
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
type
TMyRec = record
class operator Initialize(var rec: TMyRec);
class operator Finalize(var rec: TMyRec);
class operator Copy(constref source: TMyRec; var dest: TMyRec);
class operator AddRef(var rec: TMyRec);
end;
class operator TMyRec.Initialize(var rec: TMyRec);
begin
WriteLn('Initializing TMyRec at address: ', IntPtr(@rec));
end;
class operator TMyRec.Finalize(var rec: TMyRec);
begin
WriteLn('Finalizing TMyRec at address: ', IntPtr(@rec));
end;
class operator TMyRec.Copy(constref source: TMyRec; var dest: TMyRec);
begin
WriteLn('Copying TMyRec from: ', IntPtr(@source), ' to: ', IntPtr(@dest));
end;
class operator TMyRec.AddRef(var rec: TMyRec);
begin
WriteLn('Adding a TMyRec reference at address: ', IntPtr(@rec));
end;
procedure Foo(r: TMyRec);
begin
end;
procedure Test;
var
r1, r2: TMyRec;
begin
r1 := r2;
Foo(r1);
end;
Wow! Don't know that compiler is smart enough to determine such circumstances. Is this applicable to objects?
Yes and no, with COM interfaces you can use reference counting for classes:
uses
Classes;
type
IMyInterface = interface
['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
procedure Foo;
end;
TMyClass = class(TInterfacedObject, IMyInterface)
public
procedure Foo;
end;
procedure TMyClass.Foo;
begin
WriteLn('Foo on object: ', IntPtr(self));
end;
procedure Test;
var
i1, i2: IMyInterface;
begin
i1 := TMyClass.Create as IMyInterface;
i2 := i1;
i1.Foo;
i2.Foo;
end; // i1 and i2 are finalized, reducing the refcount to 0 freeing them automatically
But unlike Strings and Dynamic arrays which have strictly different member types, general classes can form circular dependencies in which case reference counting fails:
uses
Classes;
type
IMyInterface = interface
['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
procedure SetRef(ref: IMyInterface);
end;
TMyClass = class(TInterfacedObject, IMyInterface)
private
FRef: IMyInterface;
public
procedure SetRef(ref: IMyInterface);
end;
procedure TMyClass.SetRef(ref: IMyInterface);
begin
FRef:=Ref;
end;
procedure Test;
var
i1, i2: IMyInterface;
begin
i1 := TMyClass.Create as IMyInterface;
i2 := TMyClass.Create as IMyInterface;
// Create a circular reference, i1 now references i2 which references i1
i1.SetRef(i2);
i2.SetRef(i1);
end; // Because i1 has a reference to i2 and vice versa, the reference counter doesn't reach 0 so you get a memory leak
Other languages like Swift or C++ that also use reference counting for this reason introduce the concept of weak pointers, which are pointers that do not increment the reference count. In Pascal there is no such concept for COM interfaces, but you could use the Classinstance rather than the interface type for that purpose (at least in the example above):
uses
Classes;
type
IMyInterface = interface
['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
procedure SetRef(ref: IMyInterface);
end;
TMyClass = class(TInterfacedObject, IMyInterface)
private
FRef: TMyClass;
public
procedure SetRef(ref: IMyInterface);
end;
procedure TMyClass.SetRef(ref: IMyInterface);
begin
FRef:=Ref as TMyClass; // TMyClass is a raw pointer so it does not create reference counts
end;
procedure Test;
var
i1, i2: IMyInterface;
begin
i1 := TMyClass.Create as IMyInterface;
i2 := TMyClass.Create as IMyInterface;
i1.SetRef(i2);
i2.SetRef(i1);
end; // Because the circular reference uses raw pointers, the reference counter drops to 0 and there is no memory leak
Reference counting only introduces quite a small overhead and is therefore quite fast, much faster than other Garbage Collection methods. But because of this circular references, it is also a lot more complex and require quite a deep understanding of how the reference counting works. This is why most managed languages like C#, Go or Javascript usually use Garbage Colleaction instead, despite the performance penalty.
If you want to learn a bit more about reference counting, you could try to implement it yourself using management operators, it's a good exercise