Records are (unlike classes) not pointers, but describe actual memory blocks.
When using a record you need to make sure where it is allocated. If it's a global variable, it is located at the DATA segment, if it's a local variable it's on the stack. You can also place it on the heap but then you need to access it via pointers.
The memory of the stack and global variables is always freed (stack when the function returns, global variables when the program is killed).
Classes are always located on the heap and thereby behave like records if you use pointers. Basically class types are pointers in disguise. They can only be placed on the heap which makes them require manual memory management.
This has some implications. When assigning a class type, you just copying the pointer, it is just a reference to the same memory location:
c1, c2: TMyClass;
begin
c1 := TMyClass.Create;
c2 := c1;
c2.A := 'foo';
WriteLn(C1.A); // is Foo
c2.Free; // same as c1 therefore c1 is freed
With records you are actually copying the data:
r1, r2: TMyRecord;
begin
r1 := TMyRecord.Create('Foo'); // initialze A with 'Foo'
r2 := r1;
r2.A := 'Bar';
WriteLn(r1.A); // Shows 'Foo' because r2 is a copy
But with the use of Pointers this is still rather similar:
type PMyRecord = ^TMyRecord;
var r1, r2: PMyRecord;
begin
New(r1, 'Foo'); // calls create with argument 'Foo'
r2 := r1;
r2^.A := 'Bar';
WriteLn(r1^.A); // Prints 'Bar' as both reference the same record
Dispose(r2); // Same memory as r1 therefore both are freed
So I think it is better to not think of records and classes as fundamentally different with respect to memory management, but classes are just pointers to datastructures not so disimilar to records, but they are just hiding it.
But, the Destructor does not only do memory management, but also calls the destroy function which can contain arbitrary code. To simulate that you can simply add a destroy function to the record, basically the same effect.
Note, you can also emulate class like syntax with some modeswitches:
{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}
{$ModeSwitch autoderef}
type
PMyRec = ^TMyRec;
TMyRec = record
private
FA: String;
public
class function Create(const AValue: String): PMyRec; static;
procedure Destroy;
procedure Free;
property A: String read FA write FA;
end;
class function TMyRec.Create(const AValue: String): PMyRec;
var
Self: PMyRec absolute Result;
begin
New(Self);
Self.A := AValue;
end;
procedure TMyRec.Destroy;
begin
Dispose(PMyRec(@Self));
end;
procedure TMyRec.Free;
begin
if Assigned(@Self) then
Destroy;
end;
var
r1, r2: PMyRec;
begin
r1 := TMyRec.Create('Foo');
r2 := r1;
r2.A := 'Bar';
WriteLn(r1.A); // Prints Bar because this is just a pointer
r2.Free;
end.
It's not perfect because you can't add the class method Create to PMyRec (which is how class constructors work) so you have to use TMyRec for creation but PMyRec variable type, but aside from that, this is pretty much all what classes do.
From a syntactic level classes are just advanced records or objects with {$ModeSwitch autoderef} activated.
Thats why I personally think that introducing classes was a terrible idea by Borland. With respect to memory management, they are just like objects but worse as you can simply do less with them.
If you ask me, the best solution would be to just throw classes away (or deprecate them) and give all their functionality to advanced records, making both classes and objects only be there fore legacy code compatibility and having with the new advanced records a datatype model which is simply more powerful than classes thanks to more options with respect to memory managment.
And the features of autoderef could be incorporated right into the syntax:
type
PMyRec = autoderef ^TMyRec;
TMyRec = record
...
end;
var
r: PMyRec;
begin
r := PMyRec := PMyRec.Create('Foo'); // autoderef also applies to class functions
r.A := 'Bar';
r.Free;
end;
There wouldn't even be much difference to current classes on a syntactical level (if typing a few ^ is to hard for people)