Lazarus

Programming => General => Topic started by: Thaddy on August 06, 2019, 02:59:07 pm

Title: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 06, 2019, 02:59:07 pm
I revisited my old smart pointer code - from this forum -  since we have no default parameter for types other than arrays(yet).
I think I came up with a rather nice - my opinion - rewrite. Note I have also a version based on management operators, this is just the interfaced type.
It works only with classes with parameter-less constructors at the moment.

What's new:
- You don't have to use typecasts.
- Syntax for boxed is immediately obvious, more readable, therefor less prone to mix ups
- Hard casts still work too
- Assignments to a T(whateverclass) gives you a reference to the boxed instance
- You can Unbox! before lazy scope ends.

Here's an example with explanation:
Code: Pascal  [Select][+][-]
  1. program smartptrdemo;
  2. {$mode delphi}{$ifdef mswindows}{$apptype console}{$endif}{$H+}
  3. uses classes, smartptrs;
  4.  
  5. type
  6.   TAutoStringlist = Auto<Tstringlist>;
  7.  
  8. procedure TestLocal;
  9. var
  10.   l:TAutoStringList;  
  11. begin
  12.   l[box].Add('Test me, local');
  13.   writeln(L[box].text);
  14. end;  
  15.  
  16. var
  17.   a,c:TAutoStringList;
  18.   b:Tstringlist;
  19. begin
  20.   // Auto-creates a boxed stringlist if it does not exist yet.
  21.   // You can subsequently refer to the stringlist as a[box]
  22.   // This has the advantage that the syntax makes it clear
  23.   // that it is a boxed variable, automatically released.
  24.   a[box].add('test');
  25.   writeln('> ' ,a[box].text);
  26.  
  27.   // You can also manually release the boxed stringlist if required
  28.   // This can be an advantage when you want to control the release time
  29.   a[unbox];
  30.  
  31.   // This auto-creates a new stringlist that is empty
  32.   // because the previous is unboxed i.e. destroyed.
  33.   // The text property is initially empty, of course.
  34.   writeln('> ' ,a[box].text, '< should be just a space between > and <');    
  35.   a[box].Add('test some more');
  36.   writeln('> ' ,a[box].text);
  37.  
  38.   // test for local variables
  39.   TestLocal;
  40.  
  41.   // Implicit:
  42.   // If you do not want to use the [box] syntax, declare a TStrings
  43.   // and assign the boxed value to it. No need to create.
  44.   // b is now a reference to c[box] and is the boxed stringlist.
  45.   c:=default(TAutoStringlist); // shut up compiler
  46.   b:=c;
  47.  
  48.   // Explicit:
  49.   // You can hardcast Auto<T> to T
  50.   // (Again an alternative syntax)
  51.   TStringlist(c).Add('Test more');
  52.  
  53.   // c is updated, so is b, b is a reference.
  54.   writeln(b.Text);
  55.   // all cleanup is automatic
  56. end.
 


And here's the (upgraded, new) unit:
Code: Pascal  [Select][+][-]
  1. unit smartptrs;
  2. {$ifdef fpc}{$mode delphi}{$endif}
  3. interface
  4. {$ifdef fpc}{$push}{$endif}{$interfaces com}
  5. type
  6.   TBoxtype = (Box,UnBox);
  7.  
  8.   Auto<T:class, constructor> = record
  9.   strict private
  10.     FValue:T;
  11.     FFreeTheValue:IInterface;
  12.     function GetValue:T;
  13.     function GetValueFrom(v:TBoxtype):T;
  14.     type
  15.       TFreeTheValue = class(TInterfacedObject)
  16.       private
  17.         fObjectToFree:TObject;
  18.       public
  19.         constructor Create(anObjectToFree: T);
  20.         destructor Destroy;override;
  21.       end;
  22.    public
  23.      constructor Create(AValue: T);overload;
  24.      procedure Create;overload;
  25.      class operator Implicit(var smart: Auto<T>):T;
  26.      class operator Explicit(var smart: Auto<T>):T;
  27.      property value: T read GetValue;
  28.      property ValueFrom[a:TBoxType]:T read GetValueFrom;default;
  29.    end;
  30. {$ifdef fpc}{$pop 'delphi is on its own here'}{$endif}
  31.  
  32. implementation
  33.    
  34.    constructor Auto<T>.TFreeTheValue.Create(anObjectToFree:T);
  35.    begin
  36.      self.fObjectToFree := anObjectToFree;
  37.    end;
  38.    
  39.    destructor Auto<T>.TFreeTheValue.Destroy;
  40.    begin
  41.      fObjectToFree.Free;
  42.      inherited;
  43.    end;
  44.    
  45.    constructor Auto<T>.Create(AValue:T);
  46.    begin
  47.      FValue := AValue;
  48.      FFreeTheValue := TFreeTheValue.Create(FValue);
  49.    end;
  50.    
  51.    procedure Auto<T>.Create;
  52.    begin
  53.      Auto<T>.Create(T.Create);
  54.    end;
  55.      
  56.    class operator Auto<T>.Implicit(var smart: Auto<T>):T;
  57.    begin
  58.      Result := Smart.Value;
  59.    end;
  60.  
  61.    class operator Auto<T>.Explicit(var smart: Auto<T>):T;
  62.    begin
  63.      Result := Smart.Value;
  64.    end;
  65.  
  66.    function Auto<T>.GetValueFrom(v:TBoxType):T;
  67.    begin
  68.      if v = Box then Result:=GetValue else begin FFreeTheValue := nil; end;
  69.    end;
  70.    
  71.    function Auto<T>.GetValue:T;
  72.    begin
  73.      if not Assigned(FFreeTheValue) then
  74.        Self := Auto<T>.Create(T.Create);
  75.      Result := FValue;
  76.    end;
  77. end.

That's very simple code isn't it?  :)

P.S: This version is Delphi compatible, the version with management operators is not. (But has basically the same interface section and usage pattern)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avra on August 06, 2019, 11:38:45 pm
Nice code. However that boxed/unboxed code looks a little odd. Not that I couldn't adapt, but I do not find it that intuitive and natural as I would like it to be. Maybe it's just me and I do not see it properly, but I find this smart pointers example code more self explaining:
https://adugmembers.wordpress.com/2011/12/05/smart-pointers/

I am not an expert so this remark is not based on a topic knowledge, but more on a personal feeling when looking at smart pointers example code.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: lainz on August 07, 2019, 12:29:17 am
I prefer thaddy one, since it creates the object automatically.

But a non verbose option is somewhat we want too  ;) Because already defined in the type is the behaviour it will have "TAutoStringList" it says it all for me.

I can imagine an FPC automatic memory like this everywhere, it has bug consequences you can think of it?
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avk on August 07, 2019, 07:14:41 am
Nice, although it looks a bit heavier compared to managed records. It seems that this implementation does not require any additional actions when copying (I believe the implementation with managed records does)? And it seems that the modifier "default" would improve the usefulness of this primitive.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 07, 2019, 07:37:46 am
@avra That implementation is not currently possible (anonymous methods are used). The [box] syntax originated from the current lack of default properties other that array properties.
But it has actually three advantages
- It is immediately obvious you are dealing with a smart class
- It has a very clear [unbox] option, something the code you refer to lacks.
- an easy auto-create, single step. The other code demands a separate step. Both create and destroy are implicit!
@lainz Currently it only works for classes with a parameterless constructor. But indeed, a type specialization is enough.
@avk I will add the alternative implementation with advanced records later today. But that one is not Delphi compatible and that was important to me.
And yes, the default modifier would be great. That's how I came up with the boxunbox syntax (with hindsight, I actually like that...) to work around it.
Most importantly is the single variable instantiation, although assignment is also possible (b:=c in the example)

Thank you for all the remarks.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: PascalDragon on August 07, 2019, 09:18:21 am
Nice code. However that boxed/unboxed code looks a little odd. Not that I couldn't adapt, but I do not find it that intuitive and natural as I would like it to be. Maybe it's just me and I do not see it properly, but I find this smart pointers example code more self explaining:
https://adugmembers.wordpress.com/2011/12/05/smart-pointers/

I am not an expert so this remark is not based on a topic knowledge, but more on a personal feeling when looking at smart pointers example code.
Oh, wow, that is a cheeky way to do this. O.o
I should probably forward that to Blaise to make sure his implementation of anonymous functions supports that as well. :-[
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 07, 2019, 09:32:01 am
Nice code. However that boxed/unboxed code looks a little odd. Not that I couldn't adapt, but I do not find it that intuitive and natural as I would like it to be. Maybe it's just me and I do not see it properly, but I find this smart pointers example code more self explaining:
https://adugmembers.wordpress.com/2011/12/05/smart-pointers/

I am not an expert so this remark is not based on a topic knowledge, but more on a personal feeling when looking at smart pointers example code.
Oh, wow, that is a cheeky way to do this. O.o
I should probably forward that to Blaise to make sure his implementation of anonymous functions supports that as well. :-[

Yes, it is rather nice, furthermore you can build my extensions on top of it, so you still have the single assignment and immediate create. I am experimenting with it.
The one thing I miss out on is just the default for non-array properties. Hence I was more or less forced to use the Variable[box] syntax.

I am also experimenting with using rtti to support more complex constructors. (It will never be lightweight, but as a thought experiment)
Another experiment uses T(TClass) parameters which looks promising.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avra on August 07, 2019, 12:05:17 pm
@avra That implementation is not currently possible (anonymous methods are used).
Bummer.

The [box] syntax originated from the current lack of default properties other that array properties.
Hopefully we will get other then array default properties so box/unbox can be avoided.

It has a very clear [unbox] option, something the code you refer to lacks.
I thought this line from Delphi example was unboxing:
Code: Pascal  [Select][+][-]
  1. Person1 := nil; // Release early
If it is, then I find it more natural.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 07, 2019, 01:55:37 pm
@avra That implementation is not currently possible (anonymous methods are used).
Bummer.
Yes. And there is patch from Maciej.
Quote
Hopefully we will get other then array default properties so box/unbox can be avoided.
Yes.
Quote
I thought this line from Delphi example was unboxing:
Code: Pascal  [Select][+][-]
  1. Person1 := nil; // Release early
If it is, then I find it more natural.

Here I disagree, handling smartpointers should be recognizable and obvious. So a little syntactic sugar here may be warranted.
But I only realized that *after* I implemented it.

Anyway, the biggest difference is that my smart pointer is a single stop shop, not requiring a second step, which, to my mind, is how it is supposed to be.

I have currently made two changes:
- References to TObject changed to T, which was an oversight. Anyone can do that to the original sourcecode
- A very early version of RTTI (costly)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avra on August 07, 2019, 03:56:18 pm
Quote
I thought this line from Delphi example was unboxing:
Code: Pascal  [Select][+][-]
  1. Person1 := nil; // Release early
If it is, then I find it more natural.

Here I disagree, handling smartpointers should be recognizable and obvious.
I am probably missing something, but it is obvious to me. With a little macro help we can even write it like this:
Code: Pascal  [Select][+][-]
  1. Person1 := kill; // Kill object
:)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 08, 2019, 06:52:46 am
Yes, one could do that. (I actually do that internally.)
Remains the issue that the Delphi example - as is - needs extra work from the side of the programmer anyway.
But that does not mean that the technique I used can 't be incorporated into that sourcecode.

I forgot to mention that you also have - the old - access through the instance.value property if you really do not like the [box] syntax.
Code: Pascal  [Select][+][-]
  1. {$mode delphi}{$H+}
  2. uses classes, smartptrs;
  3. type
  4.   TAutoStream = Auto<Tstringlist>;
  5. var
  6.   s:TAutoStream;  
  7. begin
  8.   // same as s[box].LoadFromFile
  9.   s.Value.LoadFromFile('smartptrs.pas');
  10.   // same as s[box].text
  11.   writeln(s.value.text);
  12. end.

All the more reason to have value as a default property.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avra on August 08, 2019, 08:26:54 am
I forgot to mention that you also have - the old - access through the instance.value property if you really do not like the [box] syntax.
Yes, I would probably use that until Value is able to become default property.

I forgot to say that I much more like your Auto<> naming
Code: Pascal  [Select][+][-]
  1. type
  2.   TAutoPerson = Auto<TPerson>;
  3. var
  4.   Person1: TAutoPerson;
over the one from Delphi example:
Code: Pascal  [Select][+][-]
  1. var
  2.   Person1: ISmartPointer<TPerson>;
Using Auto is natural, clear and self explaining even for someone who hasn't heard of smart pointers before. Using ISmartPointer does not have that benefit. I would even prefer Auto over Managed, which is the only one I can think of with a better self explanation.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: ASerge on August 08, 2019, 05:26:18 pm
Thaddy, I propose a version where the structure only takes the size of a pointer. Instead of the default property and additional "Value" field, I suggest only one "Self" field.
Unit:
Code: Pascal  [Select][+][-]
  1. unit smartptrs;
  2.  
  3. {$IFDEF FPC}{$MODE DELPHI}{$ENDIF}
  4.  
  5. interface
  6.  
  7. type
  8.   TAuto<T:class, constructor> = record
  9.   strict private
  10.     FHolder: IInterface;
  11.     function GetSelf: T;
  12.     procedure SetSelf(const AValue: T);
  13.     type
  14.       THolder = class(TInterfacedObject)
  15.       public
  16.         Field: T;
  17.         constructor Create(AField: T);
  18.         destructor Destroy; override;
  19.       end;
  20.   public
  21.     constructor Create(AInstance: T);
  22.     class operator Implicit(const Other: TAuto<T>): T;
  23.     property Self: T read GetSelf write SetSelf;
  24.   end;
  25.  
  26. implementation
  27.  
  28. constructor TAuto<T>.THolder.Create(AField: T);
  29. begin
  30.   Field := AField;
  31. end;
  32.  
  33. destructor TAuto<T>.THolder.Destroy;
  34. begin
  35.   Field.Free;
  36.   inherited;
  37. end;
  38.  
  39. constructor TAuto<T>.Create(AInstance: T);
  40. begin
  41.   SetSelf(AInstance);
  42. end;
  43.  
  44. class operator TAuto<T>.Implicit(const Other: TAuto<T>): T;
  45. begin
  46.   Result := Other.Self;
  47. end;
  48.  
  49. function TAuto<T>.GetSelf: T;
  50. begin
  51.   if not Assigned(FHolder) then
  52.     SetSelf(T.Create);
  53.   Result := (FHolder as THolder).Field;
  54. end;
  55.  
  56. procedure TAuto<T>.SetSelf(const AValue: T);
  57. begin
  58.   if Assigned(AValue) then
  59.     FHolder := THolder.Create(AValue)
  60.   else
  61.     FHolder := nil;
  62. end;
  63.  
  64. end.

Example:
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. {$IFDEF FPC}{$MODE DELPHI}{$ENDIF}
  3.  
  4. uses Classes, smartptrs;
  5.  
  6. type
  7.   TAutoStringList = TAuto<TStringList>;
  8.  
  9. procedure TestLocal;
  10. var
  11.   L: TAutoStringList;
  12. begin
  13.   L.Self.Add('Test me, local');
  14.   Writeln(L.Self.Text);
  15. end;
  16.  
  17. var
  18.   a,c: TAutoStringList;
  19.   b: TStringList;
  20. begin
  21.   // Auto-creates a boxed stringlist if it does not exist yet.
  22.   // You can subsequently refer to the stringlist as a[box]
  23.   // This has the advantage that the syntax makes it clear
  24.   // that it is a boxed variable, automatically released.
  25.   a.Self.add('test');
  26.   Writeln('> ', a.Self.Text);
  27.  
  28.   // You can also manually release the boxed stringlist if required
  29.   // This can be an advantage when you want to control the release time
  30.   a.Self := nil;
  31.  
  32.   // This auto-creates a new stringlist that is empty
  33.   // because the previous is unboxed i.e. destroyed.
  34.   // The text property is initially empty, of course.
  35.   Writeln('> ', a.Self.Text, '< should be just a space between > and <');
  36.   a.Self.Add('test some more');
  37.   Writeln('> ', a.Self.text);
  38.  
  39.   // test for local variables
  40.   TestLocal;
  41.  
  42.   // Implicit:
  43.   // If you do not want to use the [box] syntax, declare a TStrings
  44.   // and assign the boxed value to it. No need to create.
  45.   // b is now a reference to c[box] and is the boxed stringlist.
  46.   c := Default(TAutoStringlist); // shut up compiler
  47.   b := c;
  48.  
  49.   // Explicit:
  50.   // You can hardcast Auto<T> to T
  51.   // (Again an alternative syntax)
  52.   TStringlist(c).Add('Test more');
  53.  
  54.   // c is updated, so is b, b is a reference.
  55.   Writeln(b.Text);
  56.   Readln;
  57.   // all cleanup is automatic
  58. end.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Zoran on August 08, 2019, 07:48:28 pm
Aren't these "smart pointers" just C++'s workaround for not having try-finally construct?
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: PascalDragon on August 09, 2019, 09:38:30 am
Aren't these "smart pointers" just C++'s workaround for not having try-finally construct?
They also allow for automatic memory management. There's simply no need then to manually free object instances except in specific cases (e.g. circular references) and even then that can be simply done by setting the smart pointer variable to Nil.
And it does avoid the clutter that is otherwise introduced by tryfinally blocks.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 09, 2019, 11:37:59 am
Thaddy, I propose a version where the structure only takes the size of a pointer. Instead of the default property and additional "Value" field, I suggest only one "Self" field.
That is possible, but relies on the record layout. First field being the same as the pointer to record itself. IMHO that is implementation detail.

Frankly I can't improve much without compiler support. ( and keep clean code)

I will consider the suggestion, though. Thx.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 10, 2019, 11:51:28 am
I have edited the unit to use T instead of TObject, as previously remarked. (Was an oversight)
Still playing with ASerge's idea.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: zamronypj on August 10, 2019, 12:57:03 pm
@Thaddy

I am trying to grasp the idea. 

Code: Pascal  [Select][+][-]
  1. TAutoStringList = TAuto<TStringList>;

CMIIW, code above works because constructor TStringList.create() does not have any parameters. If constructor has parameters, then how do you pass that required parameters to constructor?
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 10, 2019, 01:38:26 pm
As it stands (and I documented in the introduction) it is only for classes with parameter-less constructors.
However, I am working on an RTTI based version. That isn't lightweight, though. This one is.

Also note this is NOT a stop-gap for all memory management, never has been the intention.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: ASerge on August 10, 2019, 06:53:31 pm
If constructor has parameters, then how do you pass that required parameters to constructor?
Code: Pascal  [Select][+][-]
  1. Auto<T:class, constructor> = record
means: T is a class that defines a default constructor (a public parameterless constructor)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avk on August 11, 2019, 07:33:30 am
@Thaddy, I didn’t wait for your implementation with managed records and therefore decided to make my own:
Code: Pascal  [Select][+][-]
  1. unit shref;
  2.  
  3. {$mode delphi}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils;
  9.  
  10. type
  11.  
  12.   TGSharedRef<T: class, constructor> = record
  13.   private
  14.     FInstance: T;
  15.     FRefCount: PInteger;
  16.     procedure InitInstance(aValue: T);
  17.     procedure Release;
  18.     function  GetInstance: T;
  19.     procedure SetInstance(aValue: T);
  20.     class operator Initialize(var s: TGSharedRef<T>); inline;
  21.     class operator Finalize(var s: TGSharedRef<T>);
  22.     class operator Copy(constref aSrc: TGSharedRef<T>; var aDst: TGSharedRef<T>); inline;
  23.   public
  24.   type
  25.     TInstance = T;
  26.     class operator Implicit(var s: TGSharedRef<T>): T; inline;
  27.     class operator Explicit(var s: TGSharedRef<T>): T; inline;
  28.     property Instance: T read GetInstance write SetInstance;
  29.   end;
  30.  
  31. implementation
  32.  
  33. procedure TGSharedRef<T>.InitInstance(aValue: T);
  34. begin
  35.   FInstance := aValue;
  36.   if aValue <> nil then
  37.     begin
  38.       New(FRefCount);
  39.       FRefCount^ := 1;
  40.     end;
  41. end;
  42.  
  43. procedure TGSharedRef<T>.Release;
  44. begin
  45.   if FRefCount <> nil then
  46.     begin
  47.       if InterlockedDecrement(FRefCount^) = 0 then
  48.         begin
  49.           Dispose(FRefCount);
  50.           FInstance.Free;
  51.         end;
  52.       FRefCount := nil;
  53.     end;
  54. end;
  55.  
  56. function TGSharedRef<T>.GetInstance: T;
  57. begin
  58.   if FRefCount = nil then
  59.     InitInstance(T.Create);
  60.   Result := FInstance;
  61. end;
  62.  
  63. procedure TGSharedRef<T>.SetInstance(aValue: T);
  64. begin
  65.   if aValue <> FInstance then
  66.     begin
  67.       Release;
  68.       InitInstance(aValue);
  69.     end;
  70. end;
  71.  
  72. class operator TGSharedRef<T>.Initialize(var s: TGSharedRef<T>);
  73. begin
  74.   s.FRefCount := nil;
  75. end;
  76.  
  77. class operator TGSharedRef<T>.Finalize(var s: TGSharedRef<T>);
  78. begin
  79.   s.Release;
  80. end;
  81.  
  82. class operator TGSharedRef<T>.Copy(constref aSrc: TGSharedRef<T>; var aDst: TGSharedRef<T>);
  83. begin
  84.   aDst.Release;
  85.   if aSrc.FRefCount <> nil then
  86.     InterLockedIncrement(aSrc.FRefCount^);
  87.   aDst.FInstance := aSrc.Instance;
  88.   aDst.FRefCount := aSrc.FRefCount;
  89. end;
  90.  
  91. class operator TGSharedRef<T>.Implicit(var s: TGSharedRef<T>): T;
  92. begin
  93.   Result := s.Instance;
  94. end;
  95.  
  96. class operator TGSharedRef<T>.Explicit(var s: TGSharedRef<T>): T;
  97. begin
  98.   Result := s.Instance;
  99. end;
  100.  
  101. end.
  102.  

usage:
Code: Pascal  [Select][+][-]
  1. program sharedref_test;
  2.  
  3. {$mode delphi}
  4.  
  5. uses
  6.   SysUtils, shref;
  7.  
  8. type
  9.  
  10.   TTest = class
  11.   private
  12.     FValue: Integer;
  13.   public
  14.     constructor Create; overload;
  15.     constructor Create(aValue: Integer); overload;
  16.     property Value: Integer read FValue write FValue;
  17.   end;
  18.  
  19. constructor TTest.Create;
  20. begin
  21.   FValue := -1;
  22. end;
  23.  
  24. constructor TTest.Create(aValue: Integer);
  25. begin
  26.   FValue := aValue;
  27. end;
  28.  
  29. var
  30.   GlobRef: TGSharedRef<TTest>;
  31.  
  32. procedure Test;
  33. var
  34.   Ref: TGSharedRef<TTest>;
  35. begin
  36.   WriteLn('begin local proc');
  37.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  38.   TTest(Ref).Value := 101;
  39.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  40.   GlobRef := Ref;  // TTest(GlobRef).Value = 101
  41.   Ref.Instance := TTest(nil); //early release
  42.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  43.   Ref.Instance := TTest.Create(0); //parametized constructor
  44.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  45.   WriteLn('end local proc');
  46. end;
  47.  
  48. begin
  49.   Test;
  50.   WriteLn('TTest(GlobRef).Value = ', TTest(GlobRef).Value);
  51.   ReadLn;
  52. end.
  53.  
It seems it works?
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 11, 2019, 08:52:48 am
Yes. Very similar to mine based on Maciej's earlier scetch.in approach.
And just like mine it suffers from ugly hard casting (until we have a default property, that is ).

This is a good solution for FreePascal, can't be used on Delphi, though,
It generattes also slightly less code, it seems.

Nice!
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avk on August 12, 2019, 12:35:56 pm
Ok, then maybe it makes sense to add it to LGenerics.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 12, 2019, 12:51:43 pm
Fine with me.

One note: You can not really use the parameterized constructors on things like TFilestream: that does not work. Although for your TTest it works.
I think for that we need to wait for more complete RTTI if at all possible.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: avk on August 12, 2019, 01:02:20 pm
Of course, if the class does not has a parameterless constructor, then this simply will not compile.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 12, 2019, 01:21:24 pm
Of course, if the class does not have a parameterless constructor, then this simply will not compile.
Yes, but it has: TObject has..
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Zoran on August 12, 2019, 05:42:16 pm
Of course, if the class does not have a parameterless constructor, then this simply will not compile.
Yes, but it has: TObject has..

Or not: https://forum.lazarus.freepascal.org/index.php/topic,45366.msg327886.html#msg327886
 ;)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on August 12, 2019, 05:45:05 pm
Yes. But that technique is not applied.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: ASerge on August 12, 2019, 06:49:43 pm
One note: You can not really use the parameterized constructors on things like TFilestream
Option when the default constructor is not called. For diversity - FPC mode, and as and before, only the size of the pointer.
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$MODE OBJFPC}
  4. {$MODESWITCH ADVANCEDRECORDS}
  5.  
  6. interface
  7.  
  8. type
  9.   generic TAuto<T:class> = record
  10.   strict private type
  11.     TRef = record Item: T; RefCount: LongInt; end;
  12.     PRef = ^TRef;
  13.   strict private
  14.     FRef: PRef;
  15.     class operator Copy(constref Src: TAuto; var Dst: TAuto);
  16.     class operator Finalize(var Self: TAuto); inline;
  17.     class operator Initialize(var Self: TAuto); inline;
  18.     function GetInstance: T; inline;
  19.     procedure Release;
  20.     procedure SetInstance(const Value: T);
  21.   public
  22.     class operator Explicit(constref From: TAuto): T; inline;
  23.     property Instance: T read GetInstance write SetInstance;
  24.   end;
  25.  
  26. implementation
  27.  
  28. class operator TAuto.Copy(constref Src: TAuto; var Dst: TAuto);
  29. begin
  30.   Dst.Release;
  31.   if Assigned(Src.FRef) then
  32.     InterlockedIncrement(Src.FRef^.RefCount);
  33.   Dst.FRef := Src.FRef;
  34. end;
  35.  
  36. class operator TAuto.Explicit(constref From: TAuto): T;
  37. begin
  38.   Result := From.Instance;
  39. end;
  40.  
  41. class operator TAuto.Finalize(var Self: TAuto);
  42. begin
  43.   Self.Release;
  44. end;
  45.  
  46. class operator TAuto.Initialize(var Self: TAuto);
  47. begin
  48.   Self.FRef := nil;
  49. end;
  50.  
  51. function TAuto.GetInstance: T;
  52. begin
  53.   Result := nil;
  54.   if Assigned(FRef) then
  55.     Result := FRef^.Item;
  56. end;
  57.  
  58. procedure TAuto.Release;
  59. begin
  60.   if Assigned(FRef) then
  61.   begin
  62.     if InterlockedDecrement(FRef^.RefCount) = 0 then
  63.     begin
  64.       FRef^.Item.Free;
  65.       Dispose(FRef);
  66.     end;
  67.     FRef := nil;
  68.   end;
  69. end;
  70.  
  71. procedure TAuto.SetInstance(const Value: T);
  72. begin
  73.   if Assigned(FRef) then
  74.   begin
  75.     if Value = FRef^.Item then
  76.       Exit;
  77.     Release;
  78.   end;
  79.   if Assigned(Value) then
  80.   begin
  81.     New(FRef);
  82.     FRef^.Item := Value;
  83.     FRef^.RefCount := 1;
  84.   end
  85. end;
  86.  
  87. end.
Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}
  2. {$APPTYPE CONSOLE}
  3.  
  4. uses unit1;
  5.  
  6. type
  7.   TTest = class
  8.   private
  9.     FValue: Integer;
  10.   public
  11.     constructor Create(AValue: Integer); overload;
  12.     property Value: Integer read FValue write FValue;
  13.   end;
  14.  
  15. constructor TTest.Create(AValue: Integer);
  16. begin
  17.   FValue := AValue;
  18. end;
  19.  
  20. type
  21.   TAutoTest = specialize TAuto<TTest>;
  22. var
  23.   GlobRef: TAutoTest;
  24.  
  25. procedure Test;
  26. var
  27.   Ref: TAutoTest;
  28. begin
  29.   WriteLn('begin local proc');
  30.   Ref.Instance := TTest.Create(-1);
  31.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  32.   TTest(Ref).Value := 101;
  33.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  34.   GlobRef := Ref;  // TTest(GlobRef).Value = 101
  35.   Ref.Instance := nil; //early release
  36.   Ref.Instance := TTest.Create(0);
  37.   WriteLn('TTest(Ref).Value = ', TTest(Ref).Value);
  38.   WriteLn('end local proc');
  39. end;
  40.  
  41. begin
  42.   Test;
  43.   WriteLn('TTest(GlobRef).Value = ', TTest(GlobRef).Value);
  44.   ReadLn;
  45. end.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: PascalDragon on August 13, 2019, 09:19:12 am
Of course, if the class does not have a parameterless constructor, then this simply will not compile.
Yes, but it has: TObject has..

Or not: https://forum.lazarus.freepascal.org/index.php/topic,45366.msg327886.html#msg327886
 ;)
I have not yet found a Delphi example that would trigger the constructor-constraint of a generic, thus FPC parses, but ignores that constraint (my assumption is that this is from Delphi.NET times).
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 24, 2022, 05:45:52 pm
My apologies for the necroposting!

Recently, I've decided to take advantage of that smart pointers and I found there is an important operator missing, AddRef.
Cosidering the following usage as ByVal parameter:

Code: Pascal  [Select][+][-]
  1. type
  2.   TAutoTest = specialize TAuto<TTest>;
  3. var
  4.   GlobRef: TAutoTest;
  5.  
  6. procedure A(R: TAutoTest);
  7. begin
  8.   R.Instance.Value := -2;
  9. end;
  10.  
  11. begin
  12.   GlobRef.Instance := TTest.Create(-1);
  13.   A(GlobRef);
  14.   GlobRef.Instance.Value := -3; // SIGSEGV!
  15. end.
  16.  

It needs an AddRef to increase the reference count, like:
Code: Pascal  [Select][+][-]
  1. type
  2.   generic TAuto<T: class> = record
  3.    // ...
  4.   strict private
  5.    //...
  6.     class operator AddRef(var Self: TAuto); inline;
  7.    //...
  8.   end;
  9.  
  10. //...
  11.  
  12. class operator TAuto.AddRef(var Self: TAuto);
  13. begin
  14.   if Assigned(Self.FRef) then
  15.     InterlockedIncrement(Self.FRef^.RefCount);
  16. end;  
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 24, 2022, 06:18:58 pm
Do you use {$interfaces corba} by any change? I have never seen the need for that in my TAuto<>. Can you confirm you are using ,y version? and COM? Or are you using any other suggestion from the discussion? In that case I can not help you. My version works different from the code you use, that's why I ask. May be you used the version with management operators? That has issues indeed.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 24, 2022, 06:32:20 pm
Note that as long as the double finalize bug is not fixed in the compiler, management  operators do not work if they rely on Finalize.

Therefore if you want to use smartpointers you need to use COM interfaces for now.

My bug report to the double finalize issue:
https://gitlab.com/freepascal.org/fpc/source/-/issues/37164
I think I saw another bug report but the search  in gitlab is terrible. But basically if a function returns a managed  type, it will be finalized twice. As this relies on implicit casts, which are just hidden function calls, these are impacted by this. So if you use the implicit cast to create the smart pointer, it will double finalize and result in dangling pointers
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 24, 2022, 07:06:01 pm
Do you use {$interfaces corba} by any change? I have never seen the need for that in my TAuto<>. Can you confirm you are using ,y version? and COM? Or are you using any other suggestion from the discussion? In that case I can not help you. My version works different from the code you use, that's why I ask. May be you used the version with management operators? That has issues indeed.
My observations are based on code from this discussion (for TAuto<>, not for TGSharedRef<>). I'm not using {$interfaces corba}.
But because of Warfley post, may be I should double-check with other compiler versions, mine is not recent.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 24, 2022, 07:15:55 pm
As Warfley observed, the original version( by me) based on an inteface does not suffer the issue.
It is also easier to use because of auto-create (see Tstringlist example)
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 25, 2022, 01:55:18 am
As Warfley observed, the original version( by me) based on an inteface does not suffer the issue.
It is also easier to use because of auto-create (see Tstringlist example)
Correct, I can see into the compiler sources that the interfaces are handled quite differently.

I'm currently playing with the TAuto<> class from https://forum.lazarus.freepascal.org/index.php/topic,46306.msg330408.html#msg330408
 as they have a reference counter which is of my particular interest. My post was about that generics (I believe yours is named Auto<>).

@Warfley
Although not 100% sure, the issue with the double finalization (in https://gitlab.com/freepascal.org/fpc/source/-/issues/37164) is related to the creation of the hidden temp variable for the enumerator. So with the simplified example given by Serge Anvarov (ASerge?) for the same issue - when the function result is assigned to a variable, the issue dissapears, when discarded - we have double finalization.

So far, I don't see how the ASerge TAuto<> can be affected by the double finalization issue. Still investigating.


Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 07:17:20 am
You can get at the refcount from the IInterface by a cast to TInterfacedobject which is the implementing class and has a refcount. I will see if I can surface that..
Add this to the interface based Auto<T>:
Code: Pascal  [Select][+][-]
  1.    function Auto<T>.RefCount:integer;
  2.    begin
  3.      Result := 0;
  4.      if Assigned(FFreeTheValue) then
  5.        Result := (FFreeTheValue as TInterfacedObject).RefCount;
  6.    end;
Untested! Somewhat tested. Seems to work.
Now your specialization of Auto<T> has  a refcount...
Attached full unit  + demo

Again, this is ONLY for the initial interface based Auto<T> by me.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 10:12:49 am
Note, that with hindsight I kept the original [box/unbox] syntax, because that allows for explicit release, see the attached example above. My later changes where merely to satisfy customers here..  :D
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 25, 2022, 10:56:15 am
Note, that with hindsight I kept the original [box/unbox] syntax, because that allows for explicit release, see the attached example above. My later changes where merely to satisfy customers here..  :D
Thanks for the effort!
 
My preferred choice were the advanced records and I presumed all managed objects were treated in the same way by the compiler, too bad it is not the case.
In some other PL, thanks to the templates, multiple inheritance and auto objects, I'm using refcounted objects extensively and that makes my life much easier.

BTW, It is not that the same functionality can't be achieved with TCustomVariantType for example, which in turn is also IInterface by itself. But involving COM and/or Variants sounds like unneeded dead weight to me.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 11:16:49 am
This particular use of COM interfaces is very lightweight. Everything is solved at compile time, specialization is at type level.
The other option with management operators needs to have the double finalize bug fixed and ideally also default properties for other properties than arrays - also on my wishlist -. The burden would be about the same as using interfaces, in my opinion, after examining the generated assembler.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 25, 2022, 11:35:38 am
@Warfley
Although not 100% sure, the issue with the double finalization (in https://gitlab.com/freepascal.org/fpc/source/-/issues/37164) is related to the creation of the hidden temp variable for the enumerator. So with the simplified example given by Serge Anvarov (ASerge?) for the same issue - when the function result is assigned to a variable, the issue dissapears, when discarded - we have double finalization.

So far, I don't see how the ASerge TAuto<> can be affected by the double finalization issue. Still investigating.
The interesting bit about the example by Serge is that no copy operation takes place. If we use a constructor also the assignment fails:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. type
  7.  
  8.   { TSomeManagedRecord }
  9.  
  10.   TSomeManagedRecord = record
  11.     id: Integer;
  12.     class operator Initialize(var e: TSomeManagedRecord);
  13.     class operator Finalize(var e: TSomeManagedRecord);
  14.     class operator AddRef(var e: TSomeManagedRecord);
  15.     class operator Copy(constref s: TSomeManagedRecord; var d: TSomeManagedRecord);
  16.  
  17.     constructor Create(A: Integer);
  18.   end;
  19. var
  20.   nextId: Integer;
  21.  
  22. class operator TSomeManagedRecord.Initialize(var e: TSomeManagedRecord);
  23. begin
  24.   WriteLn('Initialize: ', nextId);
  25.   e.id:=nextId;
  26.   Inc(nextId);
  27. end;
  28.  
  29. class operator TSomeManagedRecord.Finalize(var e: TSomeManagedRecord);
  30. begin
  31.   WriteLn('Finalize: ', e.id);
  32. end;
  33.  
  34. class operator TSomeManagedRecord.AddRef(var e: TSomeManagedRecord);
  35. begin
  36.   WriteLn('AddRef: ', e.id);
  37. end;
  38.  
  39. class operator TSomeManagedRecord.Copy(constref s: TSomeManagedRecord;
  40.   var d: TSomeManagedRecord);
  41. begin
  42.   WriteLn('Copy: ', s.id, '->',d.id);
  43. end;
  44.  
  45. constructor TSomeManagedRecord.Create(A: Integer);
  46. begin
  47.  
  48. end;
  49.  
  50. procedure Test;
  51. var
  52.   R: TSomeManagedRecord;
  53. begin
  54.   R := TSomeManagedRecord.Create(42);
  55. end;
  56.  
  57. begin
  58.   Test;
  59.   Readln;
  60. end.
Result:
Code: Pascal  [Select][+][-]
  1. Initialize: 0
  2. Initialize: 1
  3. Finalize: 1
  4. Copy: 1->0
  5. Finalize: 0
  6. Finalize: 1
So I guess it's due to optimizations (probably RTO) which happens here that the copy is ommited, and that this optimization is not happening when the constructor is used (I already found out that constructors are weird with respect to optimization, e.g. they are never inlined).
So I think the reason why Serges example does not fail is simply because it is so simple that the optimiser circumvents the bug.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 12:10:28 pm
@Warfley,  I am a bit confused here:
1. that example does not actually assigns something useful, like a stringlist.
2. the copy operator is empty, of course there is no copy. In my example the same: only the refcount is increased, since it is a reference.
3. the create is empty too

I have no clue if ASerge's example that you cite is viable, since it seems not complete, whereas my example is a complete example that always works on any class with a default, parameterless, constructor.
I suppose - as discussed and I acknowledged - that eventually this may very well be the way to go, but as it stands my interface approach has its own merits. It is even Delphi compatible.

Maybe ASerge will expand on his example? It would be interesting to see how that works in its entirity.
It is always good to see if any of us has made any progress on the subject: smart pointers can be very useful, e.g. in object persistent frameworks.
I have no particular affinity to any of both options, as long as they work.. :D
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 25, 2022, 12:32:17 pm
I mean serges example to the bug report, not his example in this post, which was also the one  referenced in the quote I replied to (It's confusing I know :D). This is just the minimal example to show the double finalize bug
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 25, 2022, 12:34:39 pm
*snip*
I have no clue if ASerge's example that you cite is viable, since it seems not complete, whereas my example is a complete example that always works on any class with a default, parameterless, constructor.
I suppose - as discussed and I acknowledged - that eventually this may very well be the way to go, but as it stands my interface approach has its own merits. It is even Delphi compatible.
*snip
It is actually about two different examples given by ASerge - one is here for TAuto<> and another one is for the double finalization bug issue : https://gitlab.com/freepascal.org/fpc/source/uploads/43c46a9afc8394afd23b490a50216aec/project1.lpr
Warfley talks for the latter one in his latest post.

@Warfley
I'm still for the hidden temporary variables,
Code: Pascal  [Select][+][-]
  1. procedure Test1;
  2. var
  3.   R: TSomeManagedRecord;
  4. begin
  5.   R := TSomeManagedRecord.Create(42);
  6. end;
  7.  
  8. procedure Test2;
  9. var
  10.   R, S: TSomeManagedRecord;
  11. begin
  12.   R := S.Create(42);
  13. end;
Test2 doesn't suffer from the issue. Test1 involves a temporary.

In both cases fpc_copy_proc is not omitted, just fpc_finalize was erroneously called before Create in Test1.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 12:34:53 pm
Ah, OK. I did not grasp that.

Btw, I attached a renewed version of the example code (auto_refcount.pas) attachment, not the unit. Very small change to explain better, merely comments.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 25, 2022, 02:50:11 pm
@Warfley
I'm still for the hidden temporary variables,
Code: Pascal  [Select][+][-]
  1. procedure Test1;
  2. var
  3.   R: TSomeManagedRecord;
  4. begin
  5.   R := TSomeManagedRecord.Create(42);
  6. end;
  7.  
  8. procedure Test2;
  9. var
  10.   R, S: TSomeManagedRecord;
  11. begin
  12.   R := S.Create(42);
  13. end;
Test2 doesn't suffer from the issue. Test1 involves a temporary.

In both cases fpc_copy_proc is not omitted, just fpc_finalize was erroneously called before Create in Test1.
The thing is, the constructor should be nothing more than a static class function where instead of Result "self" is used. So there should not be a difference from calling the constructor of the type vs having an ordinary function with the return value being the record.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 25, 2022, 04:32:23 pm
*snip*
The thing is, the constructor should be nothing more than a static class function where instead of Result "self" is used. So there should not be a difference from calling the constructor of the type vs having an ordinary function with the return value being the record.
Exactly! In case of a managed object the memory is (should be) already pre-allocated, so when the type name is used instead of real variable, the compiler makes a hidden temporary and then the issue manifests itself. See highlighted lines 34-36, 47-49, 50-52. Lines 34-36 finalizes the temporary at ebp-56 before constructor invocation (line 39) and the actual copy (line 43):
Code: ASM  [Select][+][-]
  1. .section .text.n_p$project2_$$_test1,"x"
  2.         .balign 16,0x90
  3. .globl  P$PROJECT2_$$_TEST1
  4. P$PROJECT2_$$_TEST1:
  5. # Temps allocated between ebp-56 and ebp-8
  6. # [53] begin
  7.         pushl   %ebp
  8.         movl    %esp,%ebp
  9.         leal    -56(%esp),%esp
  10.         pushl   %ebx
  11. # Temp -8,8 allocated
  12. # Var R located at ebp-8, size=OS_64
  13.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%eax
  14.         movl    %eax,%edx
  15.         leal    -8(%ebp),%eax
  16.         call    fpc_initialize
  17.         leal    -56(%ebp),%eax
  18.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%edx
  19.         call    fpc_initialize
  20. # Temp -20,12 allocated
  21. # Temp -44,24 allocated
  22. # Temp -48,4 allocated
  23.         movl    $1,%eax
  24.         leal    -44(%ebp),%edx
  25.         leal    -20(%ebp),%ecx
  26.         call    fpc_pushexceptaddr
  27.         call    fpc_setjmp
  28.         pushl   %eax
  29.         testl   %eax,%eax
  30.         jne     .Lj16
  31. # [54] R := TSomeManagedRecord.Create(42);
  32.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%ebx
  33. # Temp -56,8 allocated
  34.         leal    -56(%ebp),%eax
  35.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%edx
  36.         call    fpc_finalize
  37.         movl    $42,%edx
  38.         leal    -56(%ebp),%eax
  39.         call    P$PROJECT2$_$TSOMEMANAGEDRECORD_$__$$_CREATE$LONGINT$$TSOMEMANAGEDRECORD
  40.         leal    -56(%ebp),%eax
  41.         leal    -8(%ebp),%edx
  42.         movl    %ebx,%ecx
  43.         call    fpc_copy_proc
  44. .Lj16:
  45.         call    fpc_popaddrstack
  46. # [55] end;
  47.         leal    -56(%ebp),%eax
  48.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%edx
  49.         call    fpc_finalize
  50.         movl    $INIT_$P$PROJECT2_$$_TSOMEMANAGEDRECORD,%edx
  51.         leal    -8(%ebp),%eax
  52.         call    fpc_finalize
  53.         popl    %eax
  54.         testl   %eax,%eax
  55.         je      .Lj15
  56.         call    fpc_reraise
  57. # Temp -44,24 released
  58. # Temp -20,12 released
  59. # Temp -48,4 released
  60. .Lj15:
  61. # Temp -8,8 released
  62.         popl    %ebx
  63.         movl    %ebp,%esp
  64.         popl    %ebp
  65.         ret

Same with the ordinary function (without using the result value).
Same with the enumerator example - the enumerator is held in a hidden temporary during the loop.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 04:51:49 pm
And that is why I think that atm the interface approach works better. No temporaries.
But that can change...
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: y.ivanov on January 25, 2022, 05:08:17 pm
And that is why I think that atm the interface approach works better. No temporaries.
But that can change...
Actually the Auto<> is also an advanced record and the issue is there. The good thing is perhaps that the subfields of the record are managed a bit deeper and a bit better by the compiler ... and this circumvents the asymmetrical Initialize/Finalize calls (no need to use them).

P.S. It seems that putting a TInterfacedObject into can be a viable workaround for the enumerator issue also.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 06:02:54 pm
Well, let's see what ASerge comes up with. This is still relevant and - I think you all agree -  a total joy to explore.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 25, 2022, 06:08:33 pm
P.S. It seems that putting a TInterfacedObject into can be a viable workaround for the enumerator issue also.
Maybe, but as enumerators are freed automatically anyway, I can simply use classes. I just wanted to try out if records might be more efficient (as they do not need to allocate memory), so there is no real advantage of using interfaces over classes
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 25, 2022, 06:25:03 pm
so there is no real advantage of using interfaces over classes
You missed the whole point about smart pointers: this can not be done with - pure - classes: automatic memory management (A.K.A. ARC, good or bad, no opinion). Automatic create on reference and automatic release....
You also seem to not fully understand interfaces and its use.. (which I find unlikely, since your contributions to the discussion, which are very valuable).
Summary:
When a class, instantiated through one of its supported interfaces and assigned to that interface variable, once the interface reference becomes nill, goes out of scope, the class will be destroyed automatically. (except for CORBA, but that is a relic/dinosour, nobody uses it apart from some forum member who wrote a brilliant - free - book about FPC/Lazarus and completely missed the point in his chapter about interfaces.... He knows my opinion to be fact...)

To me this thread is more like an academic discussion on how to achieve the best possible way to implement the topic.
Academic discussions often lead to application of such theory.
Especially the insights of ASerge are very valuable too. This is not a two way street, any suggestions and improvements (with proof!) are welcome.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 25, 2022, 10:28:01 pm
I know, this was about enumerators, where the double finalize bug also occurs. Not generally for SmarPointers
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: SymbolicFrank on January 26, 2022, 09:59:22 am
Half the reason this is an issue, is because Free doesn't nil the value. You have to use FreeAndNil consequently everywhere if you want to be able to see if a variable is freed or not. Why is that?

Another question: what would be the simplest way to make a single, specific class do automatic memory management? The one I have in mind has two constructors, both with parameters (I made the inherited ones private).
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Thaddy on January 26, 2022, 12:33:19 pm
FreeAndNil almost always simply hides bad programming. You should almost never need it in a well written program. The fpc/lazarus community seems to favor it, The Delphi community seems to use it with more caution.

There are several ways to do automatic memory management for classes, but not easily globally.
1. If applicable create the classes on the stack, I have given an example on this forum. That is very, very fast too.
2. Use my interface based Auto<T> , not any other for now. This may be different in the future as per this discussion.
3. Derive your classes from TInterfacedObject and only use interface references to create your objects.
4. Use a garbage collecting memory manager (included in ./packages/libgc ) made by Florian, and me?. I don't know how much he used from me, I merely alerted him and provided example code. He may have not used any of my code, which in turn was a FPC version of Barry Kelly's code.

It is interesting to know that Delphi dropped ARC, which is a form of global automatic memory management that does not rely on garbage collection.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: SymbolicFrank on January 27, 2022, 09:51:23 am
FreeAndNil almost always simply hides bad programming. You should almost never need it in a well written program. The fpc/lazarus community seems to favor it, The Delphi community seems to use it with more caution.

Well, it depends. Like, if you want the intersection of two StringLists:

Code: Pascal  [Select][+][-]
  1. function Both(List1, List2: TStringList): TStringList;

Obvious and easy, right? But now there are three objects that have to be freed at some point. It's clear the caller has to do that, right? But that's not where it is created. So, inserting the matching Free immediately afterwards typing the Create doesn't work.

Code: Pascal  [Select][+][-]
  1. function Both(List1, List2: string): string;

How about this? Use the TStringList.Text property. But that requires strings without line breaks. And there is more overhead.

Code: Pascal  [Select][+][-]
  1. function Both(List1, List2: TStringArray): TStringArray;

How about this? Still overhead, but at least you don't have to Free them.

Code: Pascal  [Select][+][-]
  1. function Both(List1, List2: TStringList; out These: TStringList): Boolean;

Many people think this is the way to do it. That way, the caller has to Create and Free all of them. I sometimes do this, if it is important to return a clear status, but I really don't like it. Useful in C++ style where everything generates an exception, but not in Borland style, where nil or Count = 0 are used.

If you have a class that has a property that is another class, it should be obvious that you shouldn't free it outside the encapsulating class. But you could. And it is not easy to make a copy, so you keep passing around pointers to that same class instance, or make more of the encapsulating one. Many also allow you to assign those property classes. So when do you free what if you assign the property of one to another?

Quote
It is interesting to know that Delphi dropped ARC, which is a form of global automatic memory management that does not rely on garbage collection.

I vastly prefer refcounting to garbage collection, because it keeps memory usage in check and doesn't periodically freeze your application. But I'll see if I can use the TInterfacedObject.
Title: Re: Smart pointers revisited. Let me know what you think.
Post by: Warfley on January 27, 2022, 11:59:42 am
Well, this is actually quite easy, when you have a function that returns a new object you to handle the function call as create, but free the Result in error case in the function
Code: Pascal  [Select][+][-]
  1. function Both(List1, List2: TStringList): TStringList;
  2. begin
  3.   ...
  4.   Result := TStringList.Create;
  5.   try
  6.     ...
  7.   except
  8.     Result.Free;
  9.     raise;
  10.   end;
  11. end;
  12.  
  13. // usage:
  14.   UnionList := Both(ListA, ListB);
  15.   try
  16.     ...
  17.   finally
  18.     UnionList.Free;
  19.   end;

That said, there shouldnt't be a reason one has to care for this stuff in the first place. This is very easy to get wrong, and automatic memory management is completely fine for most cases. I wish we could override the  ^ operator, this  would allow to create smartpointers accessible with the already existing pointer syntax
TinyPortal © 2005-2018