Skip to content

Instantly share code, notes, and snippets.

@Akira13641
Last active November 4, 2024 03:16
Show Gist options
  • Save Akira13641/1275e0ca886f4883c93a4f875f93a7d1 to your computer and use it in GitHub Desktop.
Save Akira13641/1275e0ca886f4883c93a4f875f93a7d1 to your computer and use it in GitHub Desktop.
Simple smart pointers in modern Object Pascal (as Free Pascal implements it)
// This unit uses record types in conjunction with record "management operators" to implement
// (pretty basic) smart pointers specifically for class types.
unit ManagedObject;
// ↓↓↓ this just enables Free Pascal's Delphi-syntax compatibility mode
{$mode Delphi}{$H+}
{$Assertions On}
interface
uses SysUtils;
type
TManagedObject<T: class> = record
public type
// ↓↓↓ type aliases for the sake of convenience
SelfT = TManagedObject<T>;
SelfP = ^SelfT;
strict private
FClassInstance: T;
FCriticalSection: TRTLCriticalSection;
private
class operator Initialize(var Val: SelfT); inline;
class operator Finalize(var Val: SelfT); inline;
class operator Copy(constref Left: SelfT; var Right: SelfT); inline;
public
function Lock: SelfP; inline;
function Unlock: SelfP; inline;
property Obj: T read FClassInstance;
end;
implementation
class operator TManagedObject<T>.Initialize(var Val: SelfT);
begin
with Val do begin
// "Create()" exists for all classes.
FClassInstance := T.Create();
InitCriticalSection(FCriticalSection);
end;
end;
class operator TManagedObject<T>.Finalize(var Val: SelfT);
begin
with Val do begin
// "Free()" exists for all classes.
FClassInstance.Free();
// We may not have called EnterCriticalSection, but that doesn't matter as LeaveCriticalSection passes through in that case.
// Note that despite the names of Windows origin, all the "critical section" methods used in this file are opaque Free Pascal-specific
// wrappers (with documented general behaviour) over various OS APIs (depending on the target we're currently building for.)
LeaveCriticalSection(FCriticalSection);
DoneCriticalSection(FCriticalSection);
end;
end;
class operator TManagedObject<T>.Copy(constref Left: SelfT; var Right: SelfT);
begin
// Implementing this operator overrides the default behaviour for when you directly
// assign one instance of a record type to another (which is to, uh, copy it.)
// We're replacing it with a no-op here so as to ensure each TManagedObject remains unique.
end;
function TManagedObject<T>.Lock: SelfP;
begin
EnterCriticalSection(FCriticalSection);
Result := @Self;
end;
function TManagedObject<T>.Unlock: SelfP;
begin
LeaveCriticalSection(FCriticalSection);
Result := @Self;
end;
end.
program ManagedObjectExample;
{$mode Delphi}{$H+}
{$Assertions On}
uses Classes, ManagedObject;
// In an overly-linear sense, "Initialize" gets called somewhere around here, I.E. before "begin".
var ListA, ListB: TManagedObject<TStringList>;
begin
// No need for any explicit constructor calls as we're using management operators to achieve everything.
Assert(ListA.Obj <> nil, 'If you see this, the compiler is broken!');
// I use "Lock()" below before the "Obj" field access only for the sake of the example,
// as we're not actually using any threads here. For "normal" access, you could just do "with ListA.Obj do begin",
// of course.
with ListA.Lock().Obj do begin
Add('AAA');
Add('BBB');
Add('CCC');
Add('DDD');
WriteLn(Text);
end;
Assert(ListB.Obj <> nil, 'If you see this, the compiler is broken!');
// If this assignment does anything at all, we have a big problem.
ListB := ListA;
Assert(ListA.Obj <> ListB.Obj, 'If you see this, the compiler is broken!');
// ListA and ListB (stack-allocated value types) free their TStringLists (heap-allocated reference types)
// and then get rid of their TRTLCriticalSections automatically somewhere right around here,
// via the "Finalize" operator we implemented.
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment