Recent

Author Topic: Smart pointers revisited. Let me know what you think.  (Read 1490 times)

Thaddy

  • Hero Member
  • *****
  • Posts: 8673
Smart pointers revisited. Let me know what you think.
« 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)
« Last Edit: August 10, 2019, 11:49:19 am by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

avra

  • Hero Member
  • *****
  • Posts: 1662
    • Additional info
Re: Smart pointers revisited. Let me know what you think.
« Reply #1 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.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

lainz

  • Hero Member
  • *****
  • Posts: 3197
    • Lainz
Re: Smart pointers revisited. Let me know what you think.
« Reply #2 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?

avk

  • Full Member
  • ***
  • Posts: 107
    • my self-education project
Re: Smart pointers revisited. Let me know what you think.
« Reply #3 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.

Thaddy

  • Hero Member
  • *****
  • Posts: 8673
Re: Smart pointers revisited. Let me know what you think.
« Reply #4 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.
« Last Edit: August 07, 2019, 08:06:45 am by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

PascalDragon

  • Hero Member
  • *****
  • Posts: 562
  • Compiler Developer
Re: Smart pointers revisited. Let me know what you think.
« Reply #5 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. :-[

Thaddy

  • Hero Member
  • *****
  • Posts: 8673
Re: Smart pointers revisited. Let me know what you think.
« Reply #6 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.
« Last Edit: August 07, 2019, 09:40:20 am by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

avra

  • Hero Member
  • *****
  • Posts: 1662
    • Additional info
Re: Smart pointers revisited. Let me know what you think.
« Reply #7 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.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

Thaddy

  • Hero Member
  • *****
  • Posts: 8673
Re: Smart pointers revisited. Let me know what you think.
« Reply #8 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)
« Last Edit: August 07, 2019, 02:02:41 pm by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

avra

  • Hero Member
  • *****
  • Posts: 1662
    • Additional info
Re: Smart pointers revisited. Let me know what you think.
« Reply #9 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
:)
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

Thaddy

  • Hero Member
  • *****
  • Posts: 8673
Re: Smart pointers revisited. Let me know what you think.
« Reply #10 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.
« Last Edit: August 08, 2019, 07:13:52 am by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

avra

  • Hero Member
  • *****
  • Posts: 1662
    • Additional info
Re: Smart pointers revisited. Let me know what you think.
« Reply #11 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.
« Last Edit: August 08, 2019, 08:47:08 am by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

ASerge

  • Hero Member
  • *****
  • Posts: 1390
Re: Smart pointers revisited. Let me know what you think.
« Reply #12 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.

Zoran

  • Hero Member
  • *****
  • Posts: 1456
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: Smart pointers revisited. Let me know what you think.
« Reply #13 on: August 08, 2019, 07:48:28 pm »
Aren't these "smart pointers" just C++'s workaround for not having try-finally construct?

PascalDragon

  • Hero Member
  • *****
  • Posts: 562
  • Compiler Developer
Re: Smart pointers revisited. Let me know what you think.
« Reply #14 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.