Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2018-03

Managed record questions

31 messages · 6 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

So in the trunk we have managed records now which is a brilliant addition and long overdue. Some questions these bring up:

1) Given the example below we have a template for a standard ref counted record. Shouldn’t there be some “meta record” type that implements this automatically? I’m thinking about adding this to some types but the idea of recreating this same template every time is daunting and records don’t have inheritance so there’s no way to capture it once.

2) This new feature begs the question why can’t classes be kept on the stack OR the heap now like in c++? There’s countless times I allocate a class on the heap (because you have to) when I could have kept it on the stack and the compiler called some destructor it when it goes out of scope. It’s such a common pattern and a shame to be dynamically allocating/freeing memory every time. C++ really cleans up a lot of memory garbage by making it an option of how your classes are allocated but FPC is stuck in the middle with structs/classes being different enough you need to decide once and commit to that memory management scheme for all uses.

type
  TManagedObject = record
  public
  	v: integer;
  private
  	refCount: integer;
  	procedure Deallocate;
  private
    class operator Initialize(var a: TManagedObject);
    class operator Finalize(var a: TManagedObject);
    class operator AddRef(var a: TManagedObject);
    class operator Copy(constref aSrc: TManagedObject; var aDst: TManagedObject);
  end;
procedure TManagedObject.Deallocate;
begin
	writeln('Deallocate');
end;
class operator TManagedObject.Initialize(var a: TManagedObject);
begin
	a.v := 1;
	a.refCount := 1;
end;
class operator TManagedObject.Finalize(var a: TManagedObject);
begin
	a.refCount -= 1;
	if a.refCount = 0 then
		a.Deallocate;
end;
class operator TManagedObject.AddRef(var a: TManagedObject);
begin
	a.refCount += 1;
end;
class operator TManagedObject.Copy(constref aSrc: TManagedObject; var aDst: TManagedObject);
begin
	aDst := aSrc;
	aDst.refCount += 1;
end;
procedure PassManagedObjects (var obj: TManagedObject);
begin
	obj.v += 1;
end;
procedure TestManagedObjects;
var
	obj: TManagedObject;
begin
	PassManagedObjects(obj);
	PassManagedObjects(obj);
	writeln('obj: ', obj.v);
end;

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph

Am 08.03.2018 04:44 schrieb "Ryan Joseph" :

1) Given the example below we have a template for a standard ref counted record. Shouldn’t there be some “meta record” type that implements this automatically? I’m thinking about adding this to some types but the idea of recreating this same template every time is daunting and records don’t have inheritance so there’s no way to capture it once.

You could use an include file containing both the declaration and implementation controlled by compiler defines together with a macro for the type name.

2) This new feature begs the question why can’t classes be kept on the stack OR the heap now like in c++? There’s countless times I allocate a class on the heap (because you have to) when I could have kept it on the stack and the compiler called some destructor it when it goes out of scope. It’s such a common pattern and a shame to be dynamically allocating/freeing memory every time. C++ really cleans up a lot of memory garbage by making it an option of how your classes are allocated but FPC is stuck in the middle with structs/classes being different enough you need to decide once and commit to that memory management scheme for all uses.

No, this doesn't beg the question, because Object Pascal classes are designed in the principle to reduce on the heap. Changing this would not only mean quite some changes in the compiler/RTL with no real gain, but it would also change a core aspect of the Object Pascal language.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 8, 2018, at 1:48 PM, Sven Barth via fpc-pascal wrote: You could use an include file containing both the declaration and implementation controlled by compiler defines together with a macro for the type name.

That’s a nice trick. If those records get adopted something “managed record” would be really cool. They’re pretty new and I’m not sure if they’re being used yet.

No, this doesn't beg the question, because Object Pascal classes are designed in the principle to reduce on the heap. Changing this would not only mean quite some changes in the compiler/RTL with no real gain, but it would also change a core aspect of the Object Pascal language.

What’s the principle that says classes should always be on the heap? The old object style classes used to be on the stack but for some reason they decided change this to heap only. I didn’t really think much about it until I saw how c++ had this optional and then it occurred to me all the Create…Free blocks I’ve used so often that could just be on the stack instead. Point taken about it being lots of work to implement though.

Btw can you override the memory manager used for class constructors? If a class reused the same block of memory that would have a similar effect and useful for tight loops where you’re allocating/deallocating memory over and over again.

Regards,
Ryan Joseph

Reply by email

Maciej Izak
to Ryan Joseph: So in the trunk we have managed records now which is a brilliant addition and long…

2018-03-08 4:13 GMT+01:00 Ryan Joseph :

Show quoted text 6 lines
1) Given the example below we have a template for a standard ref counted record. Shouldn’t there be some “meta record” type that implements this automatically? I’m thinking about adding this to some types but the idea of recreating this same template every time is daunting and records don’t have inheritance so there’s no way to capture it once.

Management operators are almost unusable without default field/default property. How it works you can see here :

https://github.com/maciej-izak/PascalSmartPointers

the examples can be tested with NewPascal for now. The simplified inheritance for records can be simulated by "default field", but the main idea is to use mix of generics/management operators and default field. "default field" has very long list of potential usages.

Show quoted text 9 lines
2) This new feature begs the question why can’t classes be kept on the stack OR the heap now like in c++? There’s countless times I allocate a class on the heap (because you have to) when I could have kept it on the stack and the compiler called some destructor it when it goes out of scope. It’s such a common pattern and a shame to be dynamically allocating/freeing memory every time. C++ really cleans up a lot of memory garbage by making it an option of how your classes are allocated but FPC is stuck in the middle with structs/classes being different enough you need to decide once and commit to that memory management scheme for all uses.

"ARC" objects are for sure possible to achieve but in other way (by new feature called concepts). Management operators are intended as base for nullable types, smart pointers and can be used for ARC classes too but the real and proper ARC classes should be implemented in other way. In this are is a lot of R&D - you need to remember that the priority is to keep backward compatibility - we don't want Delphi NEXTGEN for all modes - the main advantage of FPC is freedom of choice :)

Best regards, Maciej Izak

Reply by email

Maciej Izak
to Sven Barth: 1) Given the example below we have a template for a standard ref counted record.…

2018-03-08 7:48 GMT+01:00 Sven Barth via fpc-pascal <

Show quoted text 6 lines
: You could use an include file containing both the declaration and implementation controlled by compiler defines together with a macro for the type name.

Very improper and totally bad approach. This is for sure not the right direction for further usage of management operators.

Show quoted text 5 lines
No, this doesn't beg the question, because Object Pascal classes are designed in the principle to reduce on the heap. Changing this would not only mean quite some changes in the compiler/RTL with no real gain, but it would also change a core aspect of the Object Pascal language.

classes with auto free has a lot of sense (without modifying classes design related to usage of heap), anyway old modes should stay untouched, and the new feature should have no negative influence on performance for RTL and Object Pascal. Because of legacy of all FPC modes the topic is a little complicated but IMO is possible to achieve (for such feature new mode should be introduced [and maybe modeswitch]).

Best regards, Maciej Izak

Reply by email

Ryan Joseph
to Maciej Izak: 2018-03-08 4:13 GMT+01:00 Ryan Joseph : Management operators are almost unusable without…
Show quoted text 7 lines
On Mar 8, 2018, at 2:20 PM, Maciej Izak wrote: Management operators are almost unusable without default field/default property. How it works you can see here : https://github.com/maciej-izak/PascalSmartPointers the examples can be tested with NewPascal for now. The simplified inheritance for records can be simulated by "default field", but the main idea is to use mix of generics/management operators and default field. "default field" has very long list of potential usages.

I see, you’re using a generic record to wrap another type. That’s an interesting solution but I wonder if the heavy use of generics will be cumbersome in practical implementations. I need to try it I guess. :) Thanks.

"ARC" objects are for sure possible to achieve but in other way (by new feature called concepts). Management operators are intended as base for nullable types, smart pointers and can be used for ARC classes too but the real and proper ARC classes should be implemented in other way. In this are is a lot of R&D - you need to remember that the priority is to keep backward compatibility - we don't want Delphi NEXTGEN for all modes - the main advantage of FPC is freedom of choice :)

In many cases I don’t actually need anything on the heap and a record could be used instead but because of limitations on records in Pascal I need a full class. Doing the ARC route is just solving a problem I created by the fact I used a class instead of a record in the first place. c++ solves this by letting you keep the class on the stack or the heap depending on how you want it’s memory managed. That was a superior choice in my opinion but I understand implementing that in Pascal would be a massive overhaul and probably not worth it.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph: That’s a nice trick. If those records get adopted something “managed record” would…
Ryan Joseph wrote 16 lines
> On Mar 8, 2018, at 1:48 PM, Sven Barth via fpc-pascal wrote: > > You could use an include file containing both the declaration and implementation controlled by compiler defines together with a macro for the type name. That’s a nice trick. If those records get adopted something “managed record” would be really cool. They’re pretty new and I’m not sure if they’re being used yet. > No, this doesn't beg the question, because Object Pascal classes are designed in the principle to reduce on the heap. Changing this would not only mean quite some changes in the compiler/RTL with no real gain, but it would also change a core aspect of the Object Pascal language. What’s the principle that says classes should always be on the heap? The old object style classes used to be on the stack but for some reason they decided change this to heap only. I didn’t really think much about it until I saw how c++ had this optional and then it occurred to me all the Create…Free blocks I’ve used so often that could just be on the stack instead. Point taken about it being lots of work to implement though.

You can still use objects.

Btw can you override the memory manager used for class constructors? If a class reused the same block of memory that would have a similar effect and useful for tight loops where you’re allocating/deallocating memory over and over again.

You can do this for particular classes by overriding NewInstance.

Michael.

Reply by email

Ryan Joseph
to Ryan Joseph: I see, you’re using a generic record to wrap another type. That’s an interesting…
On Mar 8, 2018, at 2:43 PM, Ryan Joseph wrote: In many cases I don’t actually need anything on the heap and a record could be used instead but because of limitations on records in Pascal I need a full class. Doing the ARC route is just solving a problem I created by the fact I used a class instead of a record in the first place. c++ solves this by letting you keep the class on the stack or the heap depending on how you want it’s memory managed. That was a superior choice in my opinion but I understand implementing that in Pascal would be a massive overhaul and probably not worth it.

Just an example to illustrate the stupid things we do everyday without realizing it.

TList keeps some memory allocated on the heap (GetMem) and needs to be freed at the end of it’s life cycle. The class itself has an integer and a pointer (6 bytes). In the function we call TList.Create which allocates some memory from the memory manager for the 6 bytes. We add some values to the list and modify it’s internal memory (which is actually dynamic so that’s a good usage) then when the function ends we call free.

Problems:

1) We interface with the memory manager twice here and the 2nd time for a measly 6 bytes which we knew at compile time anyways (dynamic memory manager for static data??).
2) We need to remember to call Free() at the end of the function and take precaution to not exit early and leak memory (maybe even using a goto to break out of nested loops or nested functions, who knows).

Even using an ARC/garbage collecting approach for this is just stupid and a waste of resources. I know I need the TList members on the stack at compile time (integer + pointer) so why am I doing all this dynamic memory management stuff? It’s still important to use a class though because maybe I need inheritance and heap allocation for other use cases.

type
	TList = class
		private
			count: integer;
			data: pointer; // pointer to some memory on heap
		public
			procedure Add (value: pointer);
	end;
procedure MyProc;
var
	list: TList;
begin
	list := TList.Create;
	list.Add(x);
	...
	list.Free;
end;

The more efficient approach would clearly be something like:

procedure MyProc;
var
	list: stack TList; // assume this allocates us on the stack like a record
begin
	list.Clear; // there’s no Create call so init the class some how
	list.Add(x);
	// Free() is called when MyProc goes out of scope
end;

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Michael Van Canneyt: You can still use objects. You can do this for particular classes by overriding…
On Mar 8, 2018, at 3:12 PM, Michael Van Canneyt wrote: You can still use objects.

I guess I can go back to objects and use pointers to objects when I want classes? I don’t know if objects even have all the same functionality of classes, and aren’t they for backwards compatibility anyways? That doesn’t sound like the right direction to be going honestly.

It’s funny but I think the old object model was more flexible because you could make pointers to objects (if I remember correctly) but you can’t go backwards from classes to stack allocation. Deliberate or an unfortunate oversight?

Show quoted text 5 lines
> > Btw can you override the memory manager used for class constructors? If a class reused the same block of memory that would have a similar effect and useful for tight loops where you’re allocating/deallocating memory over and over again. You can do this for particular classes by overriding NewInstance.

Can you elaborate please? Not sure how that looks.

Regards,
Ryan Joseph

Reply by email

Maciej Izak
to Ryan Joseph: Just an example to illustrate the stupid things we do everyday without realizing it.…

2018-03-08 9:13 GMT+01:00 Ryan Joseph :

Show quoted text 11 lines
The more efficient approach would clearly be something like: procedure MyProc; var list: stack TList; // assume this allocates us on the stack like a record begin list.Clear; // there’s no Create call so init the class some how list.Add(x); // Free() is called when MyProc goes out of scope end;

Small ad of mORMot (main author Arnaud Bouchez aka ab) :

with SynCommons module you have nice utils for such things:

procedure MyProc;
var
  list: TList;
begin
  with TAutoFree.One(list, TList.Create) do
  begin
    list.Add(x);
    { ... some other stuff with list here }
  end;  // <-- here is called list.Free
end;

There is also nice module AutoScope which uses management operators (!) by Dmitriy Pomerantsev aka pda0:

https://github.com/pda0/AutoScope

Best regards, Maciej Izak

Reply by email

Ryan Joseph
to Maciej Izak
Show quoted text 20 lines
On Mar 8, 2018, at 4:00 PM, Maciej Izak wrote: Small ad of mORMot (main author Arnaud Bouchez aka ab) : with SynCommons module you have nice utils for such things: procedure MyProc; var list: TList; begin with TAutoFree.One(list, TList.Create) do begin list.Add(x); { ... some other stuff with list here } end; // <-- here is called list.Free end; There is also nice module AutoScope which uses management operators (!) by Dmitriy Pomerantsev aka pda0: https://github.com/pda0/AutoScope

This is a welcome improvement to the free'ing (like your example you showed) but ultimately we’re still trying to clean up the mess we made by using dynamic memory allocation in the first place. We’ve imposed a restriction on ourselves to allocate everything on the heap (even when we don’t really need it) and now we’re trying to mitigate that using even more tricks.

I know this is how it is and not likely to change I’m just pointing it out. At least we’re not Java or Swift but it’s too bad C++ beat Pascal pretty good on this front. If Pascal is going to have any standing in the modern landscape it should at least be fast and low-level otherwise it’s hard not justify using Java, C# or any other language that is reckless with memory allocation and inefficient with automatic garbage collection schemes.

Regards,
Ryan Joseph

Reply by email

Sven Barth

Am 08.03.2018 08:33 schrieb "Maciej Izak" :

No, this doesn't beg the question, because Object Pascal classes are

designed in the principle to reduce on the heap. Changing this would not only mean quite some changes in the compiler/RTL with no real gain, but it would also change a core aspect of the Object Pascal language.

classes with auto free has a lot of sense (without modifying classes design related to usage of heap), anyway old modes should stay untouched, and the new feature should have no negative influence on performance for RTL and Object Pascal. Because of legacy of all FPC modes the topic is a little complicated but IMO is possible to achieve (for such feature new mode should be introduced [and maybe modeswitch]).

I have no problem with adding a solution for automatic freeing (like ARC), my problem here was the stack vs. heap point.

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: Just an example to illustrate the stupid things we do everyday without realizing it.…

Am 08.03.2018 09:44 schrieb "Ryan Joseph" :

2) We need to remember to call Free() at the end of the function and take precaution to not exit early and leak memory (maybe even using a goto to break out of nested loops or nested functions, who knows).

You are supposed to use try...finally for this. The finally-block is always executed when the try-block is left no matter if it's by reaching the end, calling exit or by an exception.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 8, 2018, at 5:42 PM, Sven Barth via fpc-pascal wrote: You are supposed to use try...finally for this. The finally-block is always executed when the try-block is left no matter if it's by reaching the end, calling exit or by an exception.

But my greater point is why are we doing any of this at all? Try blocks, ARC, management operators, garbage collectors, all because we can’t allocate on the stack. I understand there are objects (which are missing how many features that classes have?) and records which lack a fundamental OOP feature which is inheritance.

The stack knows when the scope is terminated so it could easily free these classes for us and all too often we only need an object for the duration of a single function so allocating dynamic memory is just wasteful. I know there’s not much we can do about it now but it’s a really unfortunate oversight.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
On Mar 8, 2018, at 5:42 PM, Sven Barth via fpc-pascal < wrote: You are supposed to use try...finally for this. The finally-block is

always executed when the try-block is left no matter if it's by reaching the end, calling exit or by an exception.

But my greater point is why are we doing any of this at all? Try blocks, ARC, management operators, garbage collectors, all because we can’t allocate on the stack. I understand there are objects (which are missing how many features that classes have?) and records which lack a fundamental OOP feature which is inheritance.

The stack knows when the scope is terminated so it could easily free these classes for us and all too often we only need an object for the duration of a single function so allocating dynamic memory is just wasteful. I know there’s not much we can do about it now but it’s a really unfortunate oversight.

This is not an oversight, but an explicit design decision of the language.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 8, 2018, at 8:28 PM, Sven Barth via fpc-pascal wrote: This is not an oversight, but an explicit design decision of the language.

Do you recall what the history was of this choice? I can remember using the “new" keyword on objects but I don’t remember much more (I didn’t go full in on OOP until much later and I switched to FPC in ~2005 I think). Classes were easier to work with I can remember but why abandon the stack allocation intentionally?

Regards,
Ryan Joseph

Reply by email

Graeme Geldenhuys
to Ryan Joseph: So in the trunk we have managed records now which is a brilliant addition and long…
Ryan Joseph wrote
So in the trunk we have managed records now which is a brilliant addition and long overdue. Some questions these bring up:

My first question....
What is a managed record?

My second question....
Why would I need it?

My third question.....
What is different compared to a "normal record"

      and compared to FPC's Object data type. eg:  TSomeObject = object;

Or is this all about trying to follow Delphi again because they broke the Object Pascal "Object" type and then reintroduced that same functionality (because EMBT developers are id**ts these days) using Records, and magically called it "advanced records". aka Objects - what Turbo Pascal 7 had.

I'm getting seriously worried about where the Object Pascal language is heading. It used to be a clean and logical language. Now other languages look the part much better than Object Pascal.

Regards,
Graeme

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://fpgui.sourceforge.net/ My public PGP key: http://tinyurl.com/graeme-pgp

Reply by email

Ryan Joseph
to Graeme Geldenhuys
Show quoted text 12 lines
On Mar 9, 2018, at 3:55 AM, Graeme Geldenhuys wrote: My first question.... What is a managed record? My second question.... Why would I need it? My third question..... What is different compared to a "normal record" and compared to FPC's Object data type. eg: TSomeObject = object;

I think the proper term is "management operators” for records which basically tell you when records are going in and out scope. The obvious use for them is ARC-like systems to manage reference counting. There’s probably other interesting things you could do with them we haven’t figured out yet.

I’m still sticking with Apple’s Cocoa model of the auto-release pool that adds temporary data to a list and dumps it after every even cycle. Easy, doesn’t require new languages features. It’s a stupid waste of resources but that’s modern computing in a nutshell.

Or is this all about trying to follow Delphi again because they broke the Object Pascal "Object" type and then reintroduced that same functionality (because EMBT developers are id**ts these days) using Records, and magically called it "advanced records". aka Objects - what Turbo Pascal 7 had.

In almost every instance I can think of using so called "managed records" I could have achieved the same benefit by simply not allocating the class on the heap in the first place. Pascal has gone the way of Java or other high level languages that produce tons of garbage by allocating everything on the heap and then performing complicated tricks to dispose of all the waste they made.

c++ solves this easily by having a unified class/record model but Pascal would need to basically overhaul the entire system to make this work. Until then we’re stuck in the middle, using records when we want stack allocation (with a broken OOP model) and using Classes when we need OOP features but forced into dynamic memory management scenarios.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 25 lines
> On Mar 9, 2018, at 3:55 AM, Graeme Geldenhuys wrote: > > My first question.... > What is a managed record? > > My second question.... > Why would I need it? > > My third question..... > What is different compared to a "normal record" > and compared to FPC's Object data type. eg: TSomeObject = object; > I think the proper term is "management operators” for records which basically tell you when records are going in and out scope. The obvious use for them is ARC-like systems to manage reference counting. There’s probably other interesting things you could do with them we haven’t figured out yet. I’m still sticking with Apple’s Cocoa model of the auto-release pool that adds temporary data to a list and dumps it after every even cycle. Easy, doesn’t require new languages features. It’s a stupid waste of resources but that’s modern computing in a nutshell. > > Or is this all about trying to follow Delphi again because they broke the Object Pascal "Object" type and then reintroduced that same functionality (because EMBT developers are id**ts these days) using Records, and magically called it "advanced records". aka Objects - what Turbo Pascal 7 had. In almost every instance I can think of using so called "managed records" I could have achieved the same benefit by simply not allocating the class on the heap in the first place. Pascal has gone the way of Java or other high level languages that produce tons of garbage by allocating everything on the heap and then performing complicated tricks to dispose of all the waste they made. c++ solves this easily by having a unified class/record model but Pascal would need to basically overhaul the entire system to make this work. Until then we’re stuck in the middle, using records when we want stack allocation (with a broken OOP model) and using Classes when we need OOP features but forced into dynamic memory management scenarios.

Why don't you use objects ? No-one has obsoleted this, and you get what you want: an object that can be allocated on the stack or on the heap.

I'm sure that we can add some extension to it to get rid of the somewhat awkward new(someobject,init)
syntax to allocate one on the heap.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Mar 9, 2018, at 10:32 AM, Michael Van Canneyt wrote: Why don't you use objects ? No-one has obsoleted this, and you get what you want: an object that can be allocated on the stack or on the heap.

Object is just for backwards compatibility right? Does it support new features like generics, helpers, overloads etc…? I have read over the years they’ve been broken also so I just assumed they were a dead part of the compiler kept around for old projects. If they still worked then why introduce “advanced records” which are merely “basic classes”. :) As pointed out it doesn’t make sense that classes and records diverged in the way they did.

I'm sure that we can add some extension to it to get rid of the somewhat awkward new(someobject,init) syntax to allocate one on the heap.

If they’re not dead, then yes, making objects more flexible is possible but making classes behave like records on the stack is not. The best we can do now is use management operators on generic records to wrap classes so they get deallocated when out of scope. I can’t believe how complicated we’ve made things. :)

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
On Mar 9, 2018, at 10:32 AM, Michael Van Canneyt
Why don't you use objects ? No-one has obsoleted this, and you get what

you

want: an object that can be allocated on the stack or on the heap.

Object is just for backwards compatibility right? Does it support new features like generics, helpers, overloads etc…? I have read over the years they’ve been broken also so I just assumed they were a dead part of the compiler kept around for old projects. If they still worked then why introduce “advanced records” which are merely “basic classes”. :) As pointed out it doesn’t make sense that classes and records diverged in the way they did.

They are broken, deprecated and essentially dead in Delphi, but in FPC we've always cared for them as well.
Overloads and generics are already supported and support for helpers could be easily added. Only major feature missing compared to classes is the support for extensive RTTI.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 9, 2018, at 1:53 PM, Sven Barth via fpc-pascal wrote: They are broken, deprecated and essentially dead in Delphi, but in FPC we've always cared for them as well. Overloads and generics are already supported and support for helpers could be easily added. Only major feature missing compared to classes is the support for extensive RTTI.

Out of curiosity I did some quick tests and learned a few things:

1) Interfaces don’t work as far as I can tell (too bad)
2) The constructor/destructor situation is pretty messy (deref using ^ could be omitted like classes probably) but that’s how they were in the past.
3) I suppose it never existed but “override” missing is pretty strange. Historical I guess.
4) Move() on an object copies memory like a record (as a record does, so this is good). Does that mean they laid out in memory just like records?

So why do we have advanced records then if objects do more than records? The overhead of a virtual method table? Records have a better/cleaner syntax for operator overloads than even classes but what else?

Unlike C++ the object’s destructor was not called when it went out of scope but if it did that they would be an interesting replacement for classes in some cases. The compiler knows how to cleanup stack variables but does it call any virtual method on objects in this situation?

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 11 lines
> On Mar 9, 2018, at 1:53 PM, Sven Barth via fpc-pascal wrote: > > They are broken, deprecated and essentially dead in Delphi, but in FPC we've always cared for them as well. > Overloads and generics are already supported and support for helpers could be easily added. Only major feature missing compared to classes is the support for extensive RTTI. Out of curiosity I did some quick tests and learned a few things: 1) Interfaces don’t work as far as I can tell (too bad) 2) The constructor/destructor situation is pretty messy (deref using ^ could be omitted like classes probably) but that’s how they were in the past.

Hence my proposal to 'clean this up'.

3) I suppose it never existed but “override” missing is pretty strange. Historical I guess.

You don't need "overrid"e for objects, it is automatical.

4) Move() on an object copies memory like a record (as a record does, so this is good). Does that mean they laid out in memory just like records?

Yes, although I believe there is a hidden VMT field.

So why do we have advanced records then if objects do more than records? The overhead of a virtual method table? Records have a better/cleaner syntax for operator overloads than even classes but what else?

Simple: Delphi compatibility.

You can probably find some of my cries of despair on the mailing lists, lamenting the fact that they decided to invent something new when they had it all along since many decades :(

Unlike C++ the object’s destructor was not called when it went out of scope but if it did that they would be an interesting replacement for classes in some cases. The compiler knows how to cleanup stack variables but does it call any virtual method on objects in this situation?

I do not think so.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Mar 9, 2018, at 3:55 PM, Michael Van Canneyt wrote: Hence my proposal to 'clean this up’.

A good idea if they would be used. If objects had a couple minor things I personally could replace some records in certain parts of code and get real benefits. Someone on the compiler team would actually have to do this work though so it’s a cost/benefit analysis.

> 3) I suppose it never existed but “override” missing is pretty strange. Historical I guess. You don't need "overrid"e for objects, it is automatical.

Another mystery of why classes added this then? I personally like it since it’s an indication of an overriding and gives some compiler warnings.

> 4) Move() on an object copies memory like a record (as a record does, so this is good). Does that mean they laid out in memory just like records? Yes, although I believe there is a hidden VMT field.

Not sure how they look in memory but that code works so I guess they’re safe to use as records. I could be wrong though and cause nasty bugs using them as pure data structors (like passing to C functions!)

type
	TDataObject = object
		v: array[0..2] of integer;
	end;
procedure TestObjectCopy;
var
	obj: TDataObject;
	data: array[0..2] of integer;
	i: integer;
begin
	for i := 0 to 2 do
		obj.v[i] := 100 + (100 * i);
	Move(obj, data, sizeof(TDataObject));
	for i := 0 to 2 do
		writeln(data[i]);
end;
Show quoted text 9 lines
> > So why do we have advanced records then if objects do more than records? The overhead of a virtual method table? Records have a better/cleaner syntax for operator overloads than even classes but what else? Simple: Delphi compatibility. You can probably find some of my cries of despair on the mailing lists, lamenting the fact that they decided to invent something new when they had it all along since many decades :(

I wonder how much code is shared between objects and records in the compiler. It’s really just a syntax difference if all they really wanted was Delphi compatibility. It’s a real shame to because the time could have been spent making objects more unified with classes.

Show quoted text 5 lines
> > Unlike C++ the object’s destructor was not called when it went out of scope but if it did that they would be an interesting replacement for classes in some cases. The compiler knows how to cleanup stack variables but does it call any virtual method on objects in this situation? I do not think so.

Maciej, could you add your management operators to objects? Maybe the code for records/objects overlaps in places and it could be possible, although, the operator overload syntax for classes is not the same records (which is really a shame in itself) so perhaps not.

If you could add that then objects would have built-in ARC and since you can actually subclass them the implementation would be hidden away (instead of the messy generic wrapper we need to use with records).

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph
Show quoted text 20 lines
On Mar 9, 2018, at 4:13 PM, Ryan Joseph wrote: type TDataObject = object v: array[0..2] of integer; end; procedure TestObjectCopy; var obj: TDataObject; data: array[0..2] of integer; i: integer; begin for i := 0 to 2 do obj.v[i] := 100 + (100 * i); Move(obj, data, sizeof(TDataObject)); for i := 0 to 2 do writeln(data[i]); end;

Actually this doesn’t work. VMT table breaks the data.

Program below returns:

10
20
30
32766
0
0

with the virtual method and:

10
20
30
100
200
300

without the virtual method.

type
	TSuperDataObject = object
		a: array[0..2] of integer;
		procedure Test; virtual; abstract;
	end;
type
	TDataObject = object (TSuperDataObject)
		b: array[0..2] of integer;
		procedure Test;
	end;
procedure TDataObject.Test;
begin
end;
procedure TestObjectCopy;
var
	obj: TDataObject;
	data: array[0..5] of integer;
	i: integer;
begin
	for i := 0 to 2 do
		begin
			obj.a[i] := 10 + (10 * i);
			obj.b[i] := 100 + (100 * i);
		end;
	Move(obj, data, sizeof(TDataObject));
	for i := 0 to 5 do
		writeln(data[i]);
end;

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph: A good idea if they would be used. If objects had a couple minor things I personally…
"Ryan Joseph" wrote
> 4) Move() on an object copies memory like a record (as a record does, so

this is good). Does that mean they laid out in memory just like records?

Yes, although I believe there is a hidden VMT field.

Not sure how they look in memory but that code works so I guess they’re safe to use as records. I could be wrong though and cause nasty bugs using them as pure data structors (like passing to C functions!)

The VMT field exists if there is at least one virtual method.

> > So why do we have advanced records then if objects do more than records?

The overhead of a virtual method table? Records have a better/cleaner syntax for operator overloads than even classes but what else?

Show quoted text 6 lines
Simple: Delphi compatibility. You can probably find some of my cries of despair on the mailing lists, lamenting the fact that they decided to invent something new when they had it all along since many decades :(

I wonder how much code is shared between objects and records in the compiler. It’s really just a syntax difference if all they really wanted was Delphi compatibility. It’s a real shame to because the time could have been spent making objects more unified with classes.

Objects share roughly as much code with records as classes do as internally classes and objects share most of their code.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 9, 2018, at 4:51 PM, Sven Barth via fpc-pascal wrote: Objects share roughly as much code with records as classes do as internally classes and objects share most of their code.

I’ll be curious what Maciej says about management operators in objects then. Classes don’t have the same operator syntax as records unfortunately so that’s obviously a problem but come to think of it why are those even “operators” anyways? “Initialize” isn’t an operator in the same category as +, * etc… so I wonder why they can’t just be plain methods. Another question Maciej I guess.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
On Mar 9, 2018, at 4:51 PM, Sven Barth via fpc-pascal < wrote: Objects share roughly as much code with records as classes do as

internally classes and objects share most of their code.

I’ll be curious what Maciej says about management operators in objects then. Classes don’t have the same operator syntax as records unfortunately so that’s obviously a problem but come to think of it why are those even “operators” anyways? “Initialize” isn’t an operator in the same category as +, * etc… so I wonder why they can’t just be plain methods. Another question Maciej I guess.

Because this way the user is aware that they are something special and are not supposed to be called manually.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Mar 9, 2018, at 11:19 PM, Sven Barth via fpc-pascal wrote: Because this way the user is aware that they are something special and are not supposed to be called manually.

That makes sense.

Why can’t the compiler use a similar method to call a function when a stack based construct like a record or object goes in/out of scope? We have the management operators now (which are more complicated) and reference counted interfaces (I never used those before they sound like the current standard for “ARC” in FPC but nothing which is a low-level compiler feature.

After doing some C++ work this week it appears they simply call the constructor/destructor for stack based structs/classes when they go in/out of scope and on class member variables when their parent class goes in/out of scope. It’s very simple but cleans up so many scenarios that exist in FPC so I’m curious why we never got this feature.

Regards,
Ryan Joseph

Reply by email