Last active
November 4, 2024 03:16
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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