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).