Recent

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

Warfley

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

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Smart pointers revisited. Let me know what you think.
« Reply #46 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.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Smart pointers revisited. Let me know what you think.
« Reply #47 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...
« Last Edit: January 25, 2022, 04:56:35 pm by Thaddy »
Specialize a type, not a var.

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Smart pointers revisited. Let me know what you think.
« Reply #48 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.
« Last Edit: January 25, 2022, 05:13:41 pm by y.ivanov »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Smart pointers revisited. Let me know what you think.
« Reply #49 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.
Specialize a type, not a var.

Warfley

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

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Smart pointers revisited. Let me know what you think.
« Reply #51 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.
« Last Edit: January 25, 2022, 06:49:44 pm by Thaddy »
Specialize a type, not a var.

Warfley

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

SymbolicFrank

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

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Smart pointers revisited. Let me know what you think.
« Reply #54 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.
« Last Edit: January 26, 2022, 02:36:27 pm by Thaddy »
Specialize a type, not a var.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Smart pointers revisited. Let me know what you think.
« Reply #55 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.
« Last Edit: January 27, 2022, 10:00:32 am by SymbolicFrank »

Warfley

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

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: Smart pointers revisited. Let me know what you think.
« Reply #57 on: September 25, 2022, 07:21:47 am »
Thaddy, your smart pointer proof of concept is very cool.

I tried it with a few of my existing object pascal classes that I've written, and it appears to work very well and work as expected.

The destructors don't appear to be called when smart pointers are used in a program's begin ... end. block but they do get called when used in a separate procedure, which is fine.

However, I've only gotten classes to work that don't have arguments in their constructor.
I got around this by making a generic class that wraps one pointer and frees it in it's destructor.

But I'd prefer to have the smart pointer record handle that.

Regardless though, this is very promising (for someone coming from C++ to free pascal).

EDIT: (Well, coming back to Pascal after not using it for a couple decades, and extensively using several other programming languages in the interim)
« Last Edit: September 25, 2022, 02:05:32 pm by Bogen85 »

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Smart pointers revisited. Let me know what you think.
« Reply #58 on: September 25, 2022, 02:18:21 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.
Sorry, I somehow missed this conversation.
The problem with sudden double release is easily solved - I forgot to implement the AddRef method.
So the updated unit
Code: Pascal  [Select][+][-]
  1. unit smartptrs;
  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 AddRef(var Self: TAuto);
  16.     class operator Copy(constref Src: TAuto; var Dst: TAuto);
  17.     class operator Finalize(var Self: TAuto); inline;
  18.     class operator Initialize(var Self: TAuto); inline;
  19.     function GetInstance: T; inline;
  20.     procedure Release;
  21.     procedure SetInstance(const Value: T);
  22.   public
  23.     class operator Explicit(constref From: TAuto): T; inline;
  24.     property Instance: T read GetInstance write SetInstance;
  25.   end;
  26.  
  27. implementation
  28.  
  29. class operator TAuto.AddRef(var Self: TAuto);
  30. begin
  31.   if Assigned(Self.FRef) then
  32.     InterlockedIncrement(Self.FRef^.RefCount);
  33. end;
  34.  
  35. class operator TAuto.Copy(constref Src: TAuto; var Dst: TAuto);
  36. begin
  37.   Dst.Release;
  38.   if Assigned(Src.FRef) then
  39.     InterlockedIncrement(Src.FRef^.RefCount);
  40.   Dst.FRef := Src.FRef;
  41. end;
  42.  
  43. class operator TAuto.Explicit(constref From: TAuto): T;
  44. begin
  45.   Result := From.Instance;
  46. end;
  47.  
  48. class operator TAuto.Finalize(var Self: TAuto);
  49. begin
  50.   Self.Release;
  51. end;
  52.  
  53. class operator TAuto.Initialize(var Self: TAuto);
  54. begin
  55.   Self.FRef := nil;
  56. end;
  57.  
  58. function TAuto.GetInstance: T;
  59. begin
  60.   Result := nil;
  61.   if Assigned(FRef) then
  62.     Result := FRef^.Item;
  63. end;
  64.  
  65. procedure TAuto.Release;
  66. begin
  67.   if Assigned(FRef) then
  68.   begin
  69.     if InterlockedDecrement(FRef^.RefCount) = 0 then
  70.     begin
  71.       FRef^.Item.Free;
  72.       Dispose(FRef);
  73.     end;
  74.     FRef := nil;
  75.   end;
  76. end;
  77.  
  78. procedure TAuto.SetInstance(const Value: T);
  79. begin
  80.   if Assigned(FRef) then
  81.   begin
  82.     if Value = FRef^.Item then
  83.       Exit;
  84.     Release;
  85.   end;
  86.   if Assigned(Value) then
  87.   begin
  88.     New(FRef);
  89.     FRef^.Item := Value;
  90.     FRef^.RefCount := 1;
  91.   end;
  92. end;
  93.  
  94. end.

and the updated Test
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. {$IFDEF FPC}{$MODE OBJFPC}{$ENDIF}
  3.  
  4. uses smartptrs;
  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 TestCopyOnWrite;
  26. var
  27.   LocalRef: TAutoTest;
  28. begin
  29.   WriteLn('> TestCopyOnWrite');
  30.   LocalRef.Instance := TTest.Create(-1);
  31.   WriteLn('TTest(LocalRef).Value = ', TTest(LocalRef).Value);
  32.   TTest(LocalRef).Value := 101;
  33.   WriteLn('TTest(LocalRef).Value = ', TTest(LocalRef).Value);
  34.   Writeln('Copy Local to Global and release it');
  35.   GlobRef := LocalRef;  // TTest(GlobRef).Value = 101
  36.   LocalRef.Instance := nil; //early release
  37.   Writeln('Create new Local');
  38.   LocalRef.Instance := TTest.Create(0);
  39.   WriteLn('TTest(LocalRef).Value = ', TTest(LocalRef).Value);
  40.   WriteLn('< TestCopyOnWrite');
  41. end;
  42.  
  43. procedure TestChangeInternalField(R: TAutoTest);
  44. begin
  45.   WriteLn('> TestChangeInternalField');
  46.   R.Instance.Value := -2;
  47.   WriteLn('< TestChangeInternalField');
  48. end;
  49.  
  50. begin
  51.   GlobRef.Instance := TTest.Create(-1);
  52.   TestChangeInternalField(GlobRef);
  53.   WriteLn;
  54.   WriteLn('TTest(GlobRef).Value = ', TTest(GlobRef).Value);
  55.   WriteLn;
  56.   TestCopyOnWrite;
  57.   WriteLn;
  58.   WriteLn('TTest(GlobRef).Value = ', TTest(GlobRef).Value);
  59.   WriteLn;
  60.   ReadLn;
  61. end.

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: Smart pointers revisited. Let me know what you think.
« Reply #59 on: September 25, 2022, 08:44:31 pm »
Sorry, I somehow missed this conversation.
The problem with sudden double release is easily solved - I forgot to implement the AddRef method.
So the updated unit...

Thanks ASerge, I believe your updated unit addresses some of my questions as well. I'll try to use it.

 

TinyPortal © 2005-2018