Sign in Join

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

Mailing lists / fpc-pascal / 2017-10

Managed properties idea

18 messages · 5 people · –

Reply by email Subscribe to this list Your subscription

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

Ryan Joseph

Yesterday when I was thinking about that enumerators I had an idea for a language feature and something which I saw introduced in Objective-C some years ago.

Retaining of a member variable through a property is a common pattern I do everyday nearly. In my root class I manage a simply retain count which frees the object when it reaches 0. When assigning variables that the receiver wants to take ownership of on assignment I have to make an accessor which manages the retain count of the object (boring boiler plate).

Consider the example below where the root class (TObject) overrides a class function which performs this retain count management (ManageObject). In the other class there is a member of type TObject which TMyClass will take ownership of when assigned. If the property “Data” includes the “managed” keyword the compiler will automatically call ClassType.ManageObject() and take the return value as the variable which is written to “m_data”.

I’ve been wanting to learn how to contribute to the compiler for years now but maybe this is an easy enough project to start with. I don’t know if this is a problem people have though but I assume it may be since Objective-C had a system like for memory management and properties. What do you think? How do most FPC programmers handle memory these days?

type
	TObject = class
		class function ManageObject (obj: pointer): TObject; override;
	end;
type
	TMyClass = class (TObject)
		private
			m_data: TObject;
		public
			property Data: TObject read m_data write m_data; managed;
			// if there’s a write method defined the compiler calls ManageObject before the write method
			property MyData: TObject read m_data write SetData; managed;
	end;
class function TObject.ManageObject (obj: pointer): TObject; override;
begin
	if obj  nil then
		result := TObject(obj).Retain;
	else
		begin
			TObject(obj).Release;
			result := nil;
		end;
end;

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
"Ryan Joseph" wrote
I’ve been wanting to learn how to contribute to the compiler for years

now but maybe this is an easy enough project to start with. I don’t know if this is a problem people have though but I assume it may be since Objective-C had a system like for memory management and properties. What do you think? How do most FPC programmers handle memory these days?

The way to go in Object Pascal are reference counted interfaces (aka COM-style interfaces). After all one should program against an interface anyway and that reference counted interfaces are automatically handled by the compiler is an added bonus.

Regards,
Sven

Ryan Joseph
to Sven Barth
Show quoted text 5 lines
On Oct 5, 2017, at 12:28 PM, Sven Barth via fpc-pascal wrote: The way to go in Object Pascal are reference counted interfaces (aka COM-style interfaces). After all one should program against an interface anyway and that reference counted interfaces are automatically handled by the compiler is an added bonus.

example? I’ve been doing retain management manually and honestly I prefer that for some cases where I want more control so I still like that syntax option.

Regards,
Ryan Joseph

Marcos Douglas B. Santos
to Ryan Joseph
Ryan Joseph wrote 9 lines
> On Oct 5, 2017, at 12:28 PM, Sven Barth via fpc-pascal wrote: > > The way to go in Object Pascal are reference counted interfaces (aka COM-style interfaces). After all one should program against an interface anyway and that reference counted interfaces are automatically handled by the compiler is an added bonus. > > example? I’ve been doing retain management manually and honestly I prefer that for some cases where I want more control so I still like that syntax option.

As Sven told you, just use COM Interfaces.
Look this explanation ->
http://castle-engine.io/modern_pascal_introduction.html#_corba_and_com_types_of_interfaces but don't pay attention when to author says "Ugly (COM) interfaces" because it is not. :)

Really, it is not.

Best regards,
Marcos Douglas

Michalis Kamburelis
to Marcos Douglas B. Santos

05.10.2017 2:07 PM "Marcos Douglas B. Santos" napisał(a):

Ryan Joseph wrote 5 lines
> On Oct 5, 2017, at 12:28 PM, Sven Barth via fpc-pascal < wrote: > > The way to go in Object Pascal are reference counted interfaces (aka

COM-style interfaces). After all one should program against an interface anyway and that reference counted interfaces are automatically handled by the compiler is an added bonus.

> > example? I’ve been doing retain management manually and honestly I prefer

that for some cases where I want more control so I still like that syntax option.

As Sven told you, just use COM Interfaces.
Look this explanation ->
http://castle-engine.io/modern_pascal_introduction. html#_corba_and_com_types_of_interfaces
but don't pay attention when to author says "Ugly (COM) interfaces" because it is not. :)

Really, it is not.

I hope I explained there why I consider the COM design "ugly": because it puts three often-unrelated features (reference counting, and ability for multiple classes to expose same API safely, and interaction with COM technology) in one bag.

In this case, in which you indeed want two of these features simultaneously, I advise COM interfaces myself :) That's why they are documented after all.

In general, I would prefer these features to be available separately. Like other languages do: interfaces in Java and C# are only to "expose same API from multiple classes" (granted, comparison is unjust as Java and C# just have garbage collection) -- in Pascal you get this by CORBA interfaces. And reference counting like in e.g. C++ "shared pointers", that can wrap any class, without any additional feature (in Pascal we may get this with "management operators").

Regards,
Michalis

Marcos Douglas B. Santos
to Michalis Kamburelis
Michalis Kamburelis wrote 17 lines
05.10.2017 2:07 PM "Marcos Douglas B. Santos" napisał(a): [...] As Sven told you, just use COM Interfaces. Look this explanation -> http://castle-engine.io/modern_pascal_introduction.html#_corba_and_com_types_of_interfaces but don't pay attention when to author says "Ugly (COM) interfaces" because it is not. :) Really, it is not. I hope I explained there why I consider the COM design "ugly": because it puts three often-unrelated features (reference counting, and ability for multiple classes to expose same API safely, and interaction with COM technology) in one bag.

I think you did, but I believe that we can forget the interaction with COM even that feature was made to work with COM.
IMO, more important is the reference counting.

In this case, in which you indeed want two of these features simultaneously, I advise COM interfaces myself :) That's why they are documented after all.

I can use it only to use reference counting.

In general, I would prefer these features to be available separately. Like other languages do: interfaces in Java and C# are only to "expose same API from multiple classes" (granted, comparison is unjust as Java and C# just have garbage collection) -- in Pascal you get this by CORBA interfaces.

It would be better if these features were available separately, I agree. About the gargabe collection, we can use the referece counting for the same purpose — of course you know this, but maybe we have different opinions about how to use this feature.

I would agree that "CORBA" interfaces is more pascalish than "COM" interfaces, but I am sure that reference counting is a good feature to improve the code design.

And reference counting like in e.g. C++ "shared pointers", that can wrap any class, without any additional feature (in Pascal we may get this with "management operators").

I don't know about "management operators". Could you explain more about them?

Best regards,
Marcos Douglas

Sven Barth
to Marcos Douglas B. Santos
"Marcos Douglas B. Santos" wrote
> And reference counting like in e.g. C++ "shared pointers", that can

wrap any

> class, without any additional feature (in Pascal we may get this with > "management operators"). I don't know about "management operators". Could you explain more about

them?

Operators for records that are called when initializing, finalizing and copying that record. See $fpcdir/tests/test/tmoperator[1-10].pp

Regards,
Sven

Michalis Kamburelis
to Marcos Douglas B. Santos: On Fri, Oct 6, 2017 at 1:06 PM, Michalis Kamburelis I think you did, but I believe that…

2017-10-06 20:52 GMT+02:00 Marcos Douglas B. Santos : [...]

> In this case, in which you indeed want two of these features simultaneously, > I advise COM interfaces myself :) That's why they are documented after all. I can use it only to use reference counting.

You can, but it's a little uncomfortable.

If you use interfaces only for reference counting (not to share the API between some unrelated classes), then you define a single interface for each single class, repeating the same API -- methods, properties. It's possible, but it's just a lot of work. I have a lot of classes in my engine, and FPC RTL, FCL, Lazarus LCL define many more classes --- wrapping them all in an equivalent interface, only to get reference-counting for them, would imply a lot of work and constant maintenance.

In contrast, something like C++ "shared pointer" is a feature that can be applied to any existing class, without the need to repeat it's API. See e.g. https://en.wikipedia.org/wiki/Smart_pointer . And once we have management operators (see Sven's pointers), we can implement something like this in Pascal. So you could use something like "TSharedPointer<TStringList>" and you get a reference-counted instance of TStringList.

Regards,
Michalis

Michalis Kamburelis
to Michalis Kamburelis

2017-10-06 21:49 GMT+02:00 Michalis Kamburelis :

Show quoted text 26 lines
2017-10-06 20:52 GMT+02:00 Marcos Douglas B. Santos : [...] >> In this case, in which you indeed want two of these features simultaneously, >> I advise COM interfaces myself :) That's why they are documented after all. > > I can use it only to use reference counting. You can, but it's a little uncomfortable. If you use interfaces only for reference counting (not to share the API between some unrelated classes), then you define a single interface for each single class, repeating the same API -- methods, properties. It's possible, but it's just a lot of work. I have a lot of classes in my engine, and FPC RTL, FCL, Lazarus LCL define many more classes --- wrapping them all in an equivalent interface, only to get reference-counting for them, would imply a lot of work and constant maintenance. In contrast, something like C++ "shared pointer" is a feature that can be applied to any existing class, without the need to repeat it's API. See e.g. https://en.wikipedia.org/wiki/Smart_pointer . And once we have management operators (see Sven's pointers), we can implement something like this in Pascal. So you could use something like "TSharedPointer<TStringList>" and you get a reference-counted instance of TStringList.

P.S. It's possible to implement "shared pointers" using reference-counted COM interfaces, too, without memory management operators. You'd have something like "ISharedPointer<TStringList>", and then it's comfortable too (no need to duplicate TStringList API). So I'm not trying to say that management operators are the only way to get something like "shared pointer".

The point of my text (on
http://castle-engine.io/modern_pascal_introduction.html#_corba_and_com_types_of_interfaces ) was that if you look for a feature that gives you ability to share API between classes (and nothing else), then you want CORBA interfaces. They are equivalent to "interface concept" from Java and C#.

I feel that separation (one optional feature gives you reference-counting, some other optional feature gives you ability to share API between classes and requires to repeat this API) would be better :)

Regards,
Michalis

Maciej Izak
to Sven Barth: wrap any them? Operators for records that are called when initializing, finalizing and…

2017-10-06 21:17 GMT+02:00 Sven Barth via fpc-pascal <

Show quoted text 14 lines
: Am 06.10.2017 20:52 schrieb "Marcos Douglas B. Santos" : > > And reference counting like in e.g. C++ "shared pointers", that can wrap any > > class, without any additional feature (in Pascal we may get this with > > "management operators"). > > I don't know about "management operators". Could you explain more about them? Operators for records that are called when initializing, finalizing and copying that record. See $fpcdir/tests/test/tmoperator[1-10].pp

... and here is library with exclusive usage of "management operators" for FPC:

https://github.com/pda0/AutoScope

... and here is experimental smart pointers/objects/nullable types implementation:

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

Best regards, Maciej Izak
Michalis Kamburelis
to Maciej Izak

2017-10-06 23:55 GMT+02:00 Maciej Izak :

Show quoted text 10 lines
... and here is library with exclusive usage of "management operators" for FPC: https://github.com/pda0/AutoScope ... and here is experimental smart pointers/objects/nullable types implementation: https://github.com/maciej-izak/PascalSmartPointers

Oooh this is perfect, thank you for this. And I do mean both projects.

I see that AutoScope (
https://github.com/pda0/AutoScope/blob/master/AutoScope.pas ) is using management operators in new FPC, or internal COM interface (FGuardian) in older FPC or Delphi. Although AutoScope only "frees when outside of scope", it doesn't try to implement a full equivalent of a shared pointer. So if you store the instance of your object (like TSomeObject1 mentioned in https://github.com/pda0/AutoScope example) in some global variable / field, then you can end up with having an invalid reference (pointing to already-freed object). Unless you also store the relevant TScope instance in the same scope.

Your TSmartObj is, well, exactly what I want:) As long as you pass around only "TSmartObj<TSomeObject1>" (and don't create cycles :) ), you're perfectly safe and leak-free.

Regards,
Michalis

Marcos Douglas B. Santos
to Sven Barth: wrap any them? Operators for records that are called when initializing, finalizing and…
Sven Barth wrote 11 lines
Am 06.10.2017 20:52 schrieb "Marcos Douglas B. Santos" : > > And reference counting like in e.g. C++ "shared pointers", that can > > wrap any > > class, without any additional feature (in Pascal we may get this with > > "management operators"). > > I don't know about "management operators". Could you explain more about > them? Operators for records that are called when initializing, finalizing and copying that record. See $fpcdir/tests/test/tmoperator[1-10].pp

I saw... Looks like that now a record is a hybrid made by a record plus a class. Thanks for the info.

Regards,
Marcos Douglas

Marcos Douglas B. Santos
to Michalis Kamburelis: 2017-10-06 20:52 GMT+02:00 Marcos Douglas B. Santos : [...] You can, but it's a little…
Michalis Kamburelis wrote 17 lines
2017-10-06 20:52 GMT+02:00 Marcos Douglas B. Santos : [...] >> In this case, in which you indeed want two of these features simultaneously, >> I advise COM interfaces myself :) That's why they are documented after all. > > I can use it only to use reference counting. You can, but it's a little uncomfortable. If you use interfaces only for reference counting (not to share the API between some unrelated classes), then you define a single interface for each single class, repeating the same API -- methods, properties. It's possible, but it's just a lot of work. I have a lot of classes in my engine, and FPC RTL, FCL, Lazarus LCL define many more classes --- wrapping them all in an equivalent interface, only to get reference-counting for them, would imply a lot of work and constant maintenance.

I use interfaces because, IMO, this is the right way to write object-oriented code.
First you start with the interface that will be the contract. Then, you create one or more classes "to give life" an object which represents a real entity. Finally, every method argument or class attribute, should be an interface-type variable — most cases. The repetition here is due to the language design, but I'm OK with it. Sometimes you may have 1:1 (1 interface, 1 class) but you can have 1:N.
You (or just me) don't need to create an interface with the same methods/properties to use FPC RTL, FCL, Lazarus LCL just to wrap them to use reference-counting. This might be wrong at most cases. That is code duplication.
We already have this infrastructure in FPC RTL, FCL, Lazarus LCL — not perfect, but... — and we don't need to recreate them in our business, we just use them. So, for example, if I need to open a file, I will have an interface that represents a file in my business and then I will use a TFileStream class encapsulated in my own representation (class) of my interfaced.

Most cases we don't need the dozens methods which many classes have — even in Java or C# do that.

Show quoted text 7 lines
In contrast, something like C++ "shared pointer" is a feature that can be applied to any existing class, without the need to repeat it's API. See e.g. https://en.wikipedia.org/wiki/Smart_pointer . And once we have management operators (see Sven's pointers), we can implement something like this in Pascal. So you could use something like "TSharedPointer<TStringList>" and you get a reference-counted instance of TStringList.

OK, it sounds interested but would be a better design if a "modeswitch" turn on this as default for entire unit, ie, every instance will be reference-counted.

Best regards,
Marcos Douglas

Sven Barth
to Marcos Douglas B. Santos: On Fri, Oct 6, 2017 at 4:17 PM, Sven Barth via fpc-pascal I saw... Looks like that now a…
"Marcos Douglas B. Santos" wrote 16 lines
On Fri, Oct 6, 2017 at 4:17 PM, Sven Barth via fpc-pascal wrote: > Am 06.10.2017 20:52 schrieb "Marcos Douglas B. Santos" : >> > And reference counting like in e.g. C++ "shared pointers", that can >> > wrap any >> > class, without any additional feature (in Pascal we may get this with >> > "management operators"). >> >> I don't know about "management operators". Could you explain more about >> them? > > Operators for records that are called when initializing, finalizing and > copying that record. See $fpcdir/tests/test/tmoperator[1-10].pp I saw... Looks like that now a record is a hybrid made by a record plus a

class.

Thanks for the info.

Records can have methods and operator overloads already since 2.6.0, only the management operators are a new addition to trunk.

Regards,
Sven

Maciej Izak
to Michalis Kamburelis: 2017-10-06 23:55 GMT+02:00 Maciej Izak : Oooh this is perfect, thank you for this. And I…

2017-10-07 2:10 GMT+02:00 Michalis Kamburelis :

Your TSmartObj is, well, exactly what I want:) As long as you pass around only "TSmartObj<TSomeObject1>" (and don't create cycles :) ), you're perfectly safe and leak-free.

Probably management operators idea will be expanded by new operators Clone & Assign (names are still debatable), still some research is needed for new operators for multi-threaded aspect:

(see Eric Grange comments)
https://plus.google.com/u/0/115782878581272923715/posts/ETUoFXtoAYX

mORMot framework has also nice solution for "auto free" (solution uses interfaces too):

===code begin===
  with TAutoFree.One(o, TObject.Create) do
  begin
    ...
    Another(o2, TObject.Create);
    ...
  end;

with TAutoFree.Several([
o, TObject.Create,
f, TFoo.Create,
sl, TStringList.Create])
do

  begin
    ...
  end;

===code end===

somehow similar in usage like "using" from C#

Best regards, Maciej Izak
Marcos Douglas B. Santos
to Maciej Izak
Maciej Izak wrote 26 lines
2017-10-07 2:10 GMT+02:00 Michalis Kamburelis : > > [...] mORMot framework has also nice solution for "auto free" (solution uses interfaces too): ===code begin=== with TAutoFree.One(o, TObject.Create) do begin ... Another(o2, TObject.Create); ... end; with TAutoFree.Several([ o, TObject.Create, f, TFoo.Create, sl, TStringList.Create]) do begin ... end; ===code end=== somehow similar in usage like "using" from C#

This TAutoFree just "put" the "finally" and call .Free for an array of objects or does it something else?
Could you send the link of the implementation?

Best regards,
Marcos Douglas

Maciej Izak
to Marcos Douglas B. Santos

2017-10-09 14:43 GMT+02:00 Marcos Douglas B. Santos :

This TAutoFree just "put" the "finally" and call .Free for an array of objects or does it something else? Could you send the link of the implementation?

"One" and "Several" returns IAutoFree interface. Implementation of that interface contains TObject/array of TObject fields. TObject/array of TObject is freed when interface goes out of the scope (so yes you can omit "finally", the "Free" call for allocated objects by TAutoFree is guaranteed). Documentation:

https://synopse.info/files/html/api-1.18/SynCommons.html#TAUTOFREE https://synopse.info/files/html/Synopse%20mORMot%
20Framework%20SAD%201.18.html#TITL_130

please note that FPC behaves different than Delphi, so you can't do the Delphi trick:

===code begin===
var
  o: TFoo;
begin
  TAutoFree.One(o, TFoo.Create); // for such code interface is released at
this place in FPC
  WriteLn(o.SomeMethod); // AV i FPC
end; // in Delphi returned interface by TAutoFree.One is released here
===code end===

so correct code for FPC is:

===code begin===
var
  o: TFoo;
begin
  with TAutoFree.One(o, TFoo.Create) do
  begin
    WriteLn(o.SomeMethod);
    o.SomeProperty := 123;
  end;
end;
===code end===

Maybe at some point the first code listing will be valid in FPC too (by usage of new modeswitch SCOPEDINTERFACEDESTROY).

implementation of TAutoFree can be found here:

https://github.com/synopse/mORMot/blob/master/SynCommons.pas

Best regards, Maciej Izak
Marcos Douglas B. Santos
to Maciej Izak
Maciej Izak wrote 15 lines
2017-10-09 14:43 GMT+02:00 Marcos Douglas B. Santos : > > This TAutoFree just "put" the "finally" and call .Free for an array of > objects or does it something else? > Could you send the link of the implementation? "One" and "Several" returns IAutoFree interface. Implementation of that interface contains TObject/array of TObject fields. TObject/array of TObject is freed when interface goes out of the scope (so yes you can omit "finally", the "Free" call for allocated objects by TAutoFree is guaranteed). Documentation: https://synopse.info/files/html/api-1.18/SynCommons.html#TAUTOFREE https://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_130

Hmm, smart. Thanks for that.

Show quoted text 29 lines
please note that FPC behaves different than Delphi, so you can't do the Delphi trick: ===code begin=== var o: TFoo; begin TAutoFree.One(o, TFoo.Create); // for such code interface is released at this place in FPC WriteLn(o.SomeMethod); // AV i FPC end; // in Delphi returned interface by TAutoFree.One is released here ===code end=== so correct code for FPC is: ===code begin=== var o: TFoo; begin with TAutoFree.One(o, TFoo.Create) do begin WriteLn(o.SomeMethod); o.SomeProperty := 123; end; end; ===code end=== Maybe at some point the first code listing will be valid in FPC too (by usage of new modeswitch SCOPEDINTERFACEDESTROY).

IMHO, FPC implementation is more correct because the scope really finished. Using `with` is elegant too.

implementation of TAutoFree can be found here: https://github.com/synopse/mORMot/blob/master/SynCommons.pas

I saw, thank you again.

Best regards,
Marcos Douglas