Recent

Author Topic: freeing objects  (Read 1733 times)

ppan

  • New Member
  • *
  • Posts: 13
freeing objects
« on: February 15, 2023, 02:17:18 pm »
Hi, i have some trouble to understand the que of freeing objects. I have two objects A and B. B live in A und get with the Create Method the "Self" Pointer from Instance A. If I call the A.free, at some reasons the destructor of B ist not called, unless I call it explicit in the destructor of a. Do I understand something completely wrong? Hopefully someone can help me. Thanks in advance!


Here my Code.

Greetings

Code: Pascal  [Select][+][-]
  1. program MyLazTest;
  2.  
  3. {$mode objfpc}{$M+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  7.   cthreads,
  8.   {$ENDIF}{$ENDIF}
  9.   Classes, SysUtils, CustApp
  10.   { you can add units after this };
  11.  
  12. type
  13.   { My B }
  14.   B = class( TObject)
  15.   public
  16.     constructor Create(Sender : TObject);
  17.     destructor Destroy; override;
  18.   end;
  19.  
  20.   { My A }
  21.  
  22.   A = class( TObject)
  23.     myB_in_A : B;
  24.   public
  25.     constructor Create;
  26.     destructor Destroy; override;
  27.   end;
  28.  
  29.   { MyApplication_1 }
  30.  
  31.   MyApplication_1 = class(TCustomApplication)
  32.     myA : A;
  33.   protected
  34.     procedure DoRun; override;
  35.   public
  36.     constructor Create(TheOwner: TComponent); override;
  37.     destructor Destroy; override;
  38.     procedure WriteHelp; virtual;
  39.   end;
  40.  
  41.   constructor A.Create;
  42.   begin
  43.  
  44.     WriteLn('Object of Class A generated.');
  45.     myB_in_A := B.Create(Self);
  46.     inherited Create();
  47.   end;
  48.  
  49. destructor A.Destroy;
  50. begin
  51.  
  52.   //myB_in_A.Destroy;
  53.   WriteLn('Object of Class A released.');
  54.   inherited Destroy;
  55. end;
  56.  
  57. constructor B.Create(Sender : Tobject);
  58. begin
  59.  
  60.   WriteLn('Object of Class B generated.');
  61.   inherited Create();
  62. end;
  63.  
  64. destructor B.Destroy;
  65. begin
  66.   WriteLn('Object of Class B released.');
  67.   inherited Destroy;
  68.  
  69. end;
  70.  
  71. { MyApplication_1 }
  72.  
  73. procedure MyApplication_1.DoRun;
  74. var
  75.   ErrorMsg: String;
  76. begin
  77.   // quick check parameters
  78.   ErrorMsg:=CheckOptions('h', 'help');
  79.   if ErrorMsg<>'' then begin
  80.     ShowException(Exception.Create(ErrorMsg));
  81.     Terminate;
  82.     Exit;
  83.   end;
  84.  
  85.   // parse parameters
  86.   if HasOption('h', 'help') then begin
  87.     WriteHelp;
  88.     Terminate;
  89.     Exit;
  90.   end;
  91.   myA := A.create;
  92.   Sleep(1000);
  93.   WriteLn('Hallo...');
  94.   myA.destroy; // here I expect both destructors called
  95.   { add your program here }
  96.   Sleep(2000);
  97.   // stop program loop
  98.   Terminate;
  99. end;
  100.  
  101. constructor MyApplication_1.Create(TheOwner: TComponent);
  102. begin
  103.   inherited Create(TheOwner);
  104.   StopOnException:=True;
  105. end;
  106.  
  107. destructor MyApplication_1.Destroy;
  108. begin
  109.   inherited Destroy;
  110. end;
  111.  
  112. procedure MyApplication_1.WriteHelp;
  113. begin
  114.   { add your help code here }
  115.   writeln('Usage: ', ExeName, ' -h');
  116. end;
  117.  
  118. var
  119.   Application: MyApplication_1;
  120. begin
  121.   Application:=MyApplication_1.Create(nil);
  122.   Application.Title:='My Application';
  123.   Application.Run;
  124.   Application.Free;
  125. end.
  126.  

Blaazen

  • Hero Member
  • *****
  • Posts: 3237
  • POKE 54296,15
    • Eye-Candy Controls
Re: freeing objects
« Reply #1 on: February 15, 2023, 02:48:17 pm »
1) Do not call myA.destroy;. Use myA.Free; instead.
2) No. You have to free myB_in_A in destructor of A, i.e.
Code: Pascal  [Select][+][-]
  1. destructor A.Destroy;
  2. begin
  3.  
  4.   myB_in_A.Free;
  5.   WriteLn('Object of Class A released.');
  6.   inherited Destroy;
  7. end;
  8.  
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

ppan

  • New Member
  • *
  • Posts: 13
Re: freeing objects
« Reply #2 on: February 15, 2023, 03:00:37 pm »
Hi and thx for your replay.

I'm wondering abot that becaus I thought that A "owns" B and therefor calls B.free if A.free is called. Because of that I give B the "owner" of A with the self-Pointer:
Code: Pascal  [Select][+][-]
  1. myB_in_A := B.Create(Self);

I found this in this post and try to replicate:

https://forum.lazarus.freepascal.org/index.php?topic=30016.0

Greetings

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2007
  • Fifty shades of code.
    • Delphi & FreePascal
Re: freeing objects
« Reply #3 on: February 15, 2023, 03:19:59 pm »
If you would call Free it would Free, as the name implies it.  :P
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: freeing objects
« Reply #4 on: February 15, 2023, 03:23:38 pm »
FreePascal does not have an owner concept by default, but there are classes that implement this. Namely TComponent. If you inherit from TComponent it can own and manage other compontents:
Code: Pascal  [Select][+][-]
  1. type
  2.   TFoo = class(TComponent)
  3.   public destructor Destroy; override;
  4.   end;
  5.  
  6.   TBar = class(TComponent)
  7.   public destructor Destroy; override;
  8.   end;
  9.  
  10.   TFooBar = class(TComponent)
  11.   private
  12.     FFoo: TFoo;
  13.     FBar: TBar;
  14.   public
  15.     constructor Create(AOwner: TComponent); override;
  16.     destructor Destroy; override;
  17.   end;
  18.  
  19. destructor TFoo.Destroy;
  20. begin
  21.   WriteLn('TFoo.Destroy');
  22.   inherited Destroy;
  23. end;
  24.  
  25. destructor TBar.Destroy;
  26. begin          
  27.   WriteLn('TBar.Destroy');
  28.   inherited Destroy;
  29. end;
  30.  
  31. constructor TFooBar.Create(AOwner: TComponent);
  32. begin
  33.   inherited Create(AOwner);
  34.   FFoo := TFoo.Create(Self);
  35.   FBar := TBar.Create(Self);
  36. end;
  37.  
  38. destructor TFooBar.Destroy;
  39. begin
  40.   inherited Destroy; // Will destroy all owned items
  41.   WriteLn('TFooBar.Destroy');
  42. end;
  43.  
  44. begin
  45.   TFooBar.Create(nil).Free;

Result:
Code: Text  [Select][+][-]
  1. TBar.Destroy
  2. TFoo.Destroy
  3. TFooBar.Destroy

ppan

  • New Member
  • *
  • Posts: 13
Re: freeing objects
« Reply #5 on: February 15, 2023, 03:51:18 pm »
Ah ok thanks! Didn't get that tiny difference. So basically as long as I do not use TComponent, I have to Free ever thing created with "Create" by myself?!

Strange that if freeing is so essential that this isn't done by automation. Or do I understand something complete wrong?

Thanks!

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: freeing objects
« Reply #6 on: February 15, 2023, 04:16:02 pm »
Ah ok thanks! Didn't get that tiny difference. So basically as long as I do not use TComponent, I have to Free ever thing created with "Create" by myself?!

Strange that if freeing is so essential that this isn't done by automation. Or do I understand something complete wrong?

Thanks!

It can be automated.
See this: https://forum.lazarus.freepascal.org/index.php/topic,46306.0.html
« Last Edit: February 15, 2023, 05:13:41 pm by Bogen85 »

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: freeing objects
« Reply #7 on: February 15, 2023, 04:50:03 pm »
Strange that if freeing is so essential that this isn't done by automation. Or do I understand something complete wrong?
Freeing is automated for some instances, COM interfaces, dynamic arrays and strings are automatically managed through reference counting, which is how it is also often done in other languages such as swift. But because when developing Delphi with it's new class based OOP system, Embarcadero decided that managing memory manually is so fun that they don't want to include it for classes (even though as already mentioned it is already widely used for other types), we are now stuck with it for all class instances.

Automatic memory management can be emulated, by using Interfaces (as with in the Link that Bogen85 posted), or the new managed records (even though they still have the double finalize bug), but it always requires encapsulation into managed types.

When you write your own class, you can write an interface for it and only use it through that COM interface. This is what I did for my Iterator library because it allows to use temporary objects without the need of manual freeing. Which allows doing things like:
Code: Pascal  [Select][+][-]
  1.   for i in Filter<Integer>(Iterate<Integer>(Data), isEven) do
  2.     Write(' ', i);
instead of:
Code: Pascal  [Select][+][-]
  1. DataIterator := Iterate<Integer>(Data);
  2. FilterIterator := Filter<Integer>(DataIterator, isEven);
  3. try
  4.   for i in FilterIterator do
  5.     Write(' ', i);
  6. finally
  7.   FilterIterator.Free;
  8.   DataIterator.Free;
  9. end;
It's just much cleaner.

Another great example on how Interfaces can be used to automate memory managment is the gmp unit, because automatically managed memory also means you can have operator overloading (which requires temporary objects), which otherwise is not possible with classes (as this would inevetibly lead to memory leaks), so the MPInteger interface allows the following:
Code: Pascal  [Select][+][-]
  1. var
  2.   a, b, c: MPInteger;
  3. begin
  4.   a := 42;
  5.   b := 314;
  6.   c := a * 2 + b
  7. end;
Instead of:
Code: Pascal  [Select][+][-]
  1. var
  2.   a, b, c, temp: TMPInteger;
  3. begin
  4.   a := TMPInteger.Create(42);
  5.   b := TMPInteger.Create(314);
  6.   temp := TMPInteger.Create(a);
  7.   temp.Multiply(2);
  8.   c := TMPInteger.Create(temp);
  9.   c.Add(b);
  10.  
  11.   a.Free;
  12.   b.Free;
  13.   temp.Free;
  14.   c.Free;
  15. end;
« Last Edit: February 15, 2023, 04:52:23 pm by Warfley »

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: freeing objects
« Reply #8 on: February 15, 2023, 05:09:59 pm »
Automatic memory management can be emulated, by using Interfaces (as with in the Link that Bogen85 posted), or the new managed records (even though they still have the double finalize bug), but it always requires encapsulation into managed types.

I keep hearing about the double finalize bug thing, but I've not ran into it myself yet...
I'll need to look into that... (and why I don't get more than one finalize)...


EDIT: OK, looking at this: https://gitlab.com/freepascal.org/fpc/source/-/issues/37164
« Last Edit: February 15, 2023, 05:13:53 pm by Bogen85 »

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: freeing objects
« Reply #9 on: February 15, 2023, 05:31:08 pm »
I keep hearing about the double finalize bug thing, but I've not ran into it myself yet...
I'll need to look into that... (and why I don't get more than one finalize)...


EDIT: OK, looking at this: https://gitlab.com/freepascal.org/fpc/source/-/issues/37164

I've originally discovered it for using enumerators, which is shown in the issue you've linked, with this I then simply used classes or interfaces for enumerators (as enumerators are also automatically freed this is not a problem), but later I also discovered that this bug also occurs in other places, with constructors:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. uses
  7.   {$IFDEF UNIX}
  8.   cthreads,
  9.   {$ENDIF}
  10.   Classes
  11.   { you can add units after this };
  12.  
  13. type
  14.  
  15.   { TTestRec }
  16.  
  17.   TTestRec = record
  18.   Value: Integer;
  19.   class operator Initialize(var rec: TTestRec);
  20.   class operator Finalize(var rec: TTestRec);
  21.   class operator Copy(constref src: TTestRec; var dst: TTestRec);
  22.   class operator AddRef(var rec: TTestRec);
  23.   constructor Create(AValue: Integer);
  24.   end;
  25.  
  26. { TTestRec }
  27.  
  28. class operator TTestRec.Initialize(var rec: TTestRec);
  29. begin
  30.   WriteLn('Init');
  31. end;
  32.  
  33. class operator TTestRec.Finalize(var rec: TTestRec);
  34. begin
  35.   WriteLn('Final');
  36. end;
  37.  
  38. class operator TTestRec.Copy(constref src: TTestRec; var dst: TTestRec);
  39. begin
  40.   WriteLn('Copy');
  41. end;
  42.  
  43. class operator TTestRec.AddRef(var rec: TTestRec);
  44. begin
  45.   WriteLn('AddRef');
  46. end;
  47.  
  48. constructor TTestRec.Create(AValue: Integer);
  49. begin
  50.   Self.Value := AValue;
  51. end;
  52.  
  53. procedure Test;
  54. var
  55.   rec: TTestRec;
  56. begin
  57.   rec := TTestRec.Create(42);
  58. end;
  59.  
  60. begin
  61.   Test;
  62.   ReadLn;
  63. end.
  64.  
2 Initializes but 3 finalizes.

And this was quite a discovery process, as I was already working on a project for 2 weekends full time, so I probably spend already like 30-40 hours into a project, and I've noticed that the values where not quite right, but I could not figure out why. Turns out that after the double finalize, the memory was reallocated by something different, so I had multiple record pointers all using the same memory region, without knowing, which meant did not cause any crashs, just that the values did not make sense.

It took me another 10 hours to figure that out, since then I haven't touched managed records (at least those that rely on finalize) again, because such bugs are really hard to find, they usually start occuring deep inside the project, and can cost days to debug.

I really really like the idea of managed records, the project where I discovered this was literally a whole library of types using managed records, lists, dictionairies and sets, file and network streams, etc.
I still think that this would allow for really great and clean code, things like this:
Code: Pascal  [Select][+][-]
  1. function ReadFile(const AFileName: String): String;
  2. begin
  3.   Result := '';
  4.   with FileOpen(AFileName, fomRead) do
  5.      while not EOF do
  6.        Result += ReadLine + NewLine;
  7. end;
But sadly until the double finalize bug is fixed, I don't want to start using them again, just to again after hours upon hours, finding out that something deep down went wrong and I can start completely from scratch.

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: freeing objects
« Reply #10 on: February 15, 2023, 06:11:27 pm »
I've originally discovered it for using enumerators, which is shown in the issue you've linked, with this I then simply used classes or interfaces for enumerators (as enumerators are also automatically freed this is not a problem), but later I also discovered that this bug also occurs in other places, with constructors...

Alright. I think I understand then why I've not run into the bug yet...

Since to me the when I declare the record variable, it already exists when the function starts, so I don't need to create it, I just need to update fields.
My finalizers check for initialized fields, so if they are initialized to the defaults, they are benign.
I do have "construction" functions that return advanced records, but they don't use constructors, because to me, once again, at the start of that "creation" function, the result variable is already "allocated".

But the finalizer is not called in the functions that return the advanced records, even if that those "construction" functions update the fields.
It is called in the functions that receive those, when they go out of scope.

I guess I've just somehow avoided it so far.
On the constructor issue, primarily because to me construction with actual constructors makes no sense if the record is already allocated.

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: freeing objects
« Reply #11 on: February 15, 2023, 06:34:53 pm »
Code: Pascal  [Select][+][-]
  1. program finalizer_ex;
  2. {$mode objfpc}
  3. {$H+}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. type
  7.  
  8.   { TTestRec }
  9.   TTestRec = record
  10.   strict private
  11.     value: integer;
  12.   public
  13.     procedure Print;
  14.     class operator initialize (var rec: TTestRec);
  15.     class operator Finalize (var rec: TTestRec);
  16.     class operator Copy (constref src: TTestRec; var dst: TTestRec);
  17.     class operator AddRef (var rec: TTestRec);
  18.     class function Make (AValue: integer): TTestRec; static;
  19.   end;
  20.  
  21. { TTestRec }
  22. class operator TTestRec.initialize (var rec: TTestRec);
  23.   begin
  24.     writeln('Init ', rec.value);
  25.   end;
  26.  
  27. class operator TTestRec.Finalize (var rec: TTestRec);
  28.   begin
  29.     writeln('Final ', rec.value);
  30.   end;
  31.  
  32. class operator TTestRec.Copy (constref src: TTestRec; var dst: TTestRec);
  33.   begin
  34.     writeln('Copy ', src.value, ' ', dst.value);
  35.   end;
  36.  
  37. class operator TTestRec.AddRef (var rec: TTestRec);
  38.   begin
  39.     writeln('AddRef ', rec.value);
  40.   end;
  41.  
  42. class function TTestRec.Make (AValue: integer): TTestRec; static;
  43.   begin
  44.     result.value := AValue;
  45.   end;
  46.  
  47. procedure TTestRec.Print;
  48.   begin
  49.     writeln('Value: ', value);
  50.   end;
  51.  
  52. procedure Test;
  53.   var
  54.     rec: TTestRec;
  55.   begin
  56.     writeln('before Make');
  57.     rec := TTestRec.Make(42);
  58.     rec.Print;
  59.     writeln('after Make');
  60.   end;
  61.  
  62. begin
  63.   writeln('before Test');
  64.   Test;
  65.   writeln('after Test');
  66.   ReadLn;
  67. end.
  68. // CudaText: lexer_file=Pascal; tab_size=2; tab_spaces=Yes; newline=LF;

For me the finalizer is only called once:

Code: Text  [Select][+][-]
  1. before Test
  2. Init 0
  3. before Make
  4. Value: 42
  5. after Make
  6. Final 42
  7. after Test

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: freeing objects
« Reply #12 on: February 15, 2023, 07:11:21 pm »
Ok, I tried your example.
2 Initializes but 3 finalizes.

For me anything other than 1 initialize and 1 finalize I'd consider flawed...
That they don't even match is even more flawed...

But sadly until the double finalize bug is fixed, I don't want to start using them again, just to again after hours upon hours, finding out that something deep down went wrong and I can start completely from scratch.

I understand. I'm just going to avoid the problem is I can. To make the create makes no sense unless I fully use the constructor in the declaration section, because to me, as I stated earlier, it is already "constructed" at the start of the scope, so I have no reason to "construct" it again, even if I could not provide the parameter to the first construction.

For me it is the constructors for advanced records that is broken... But that does not explain the double finalize.. (and why I don't see it??).

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: freeing objects
« Reply #13 on: February 15, 2023, 07:18:47 pm »
Ok, I tried your example.
2 Initializes but 3 finalizes.

For me anything other than 1 initialize and 1 finalize I'd consider flawed...
That they don't even match is even more flawed...

But sadly until the double finalize bug is fixed, I don't want to start using them again, just to again after hours upon hours, finding out that something deep down went wrong and I can start completely from scratch.

I understand. I'm just going to avoid the problem is I can. To make the create makes no sense unless I fully use the constructor in the declaration section, because to me, as I stated earlier, it is already "constructed" at the start of the scope, so I have no reason to "construct" it again, even if I could not provide the parameter to the first construction.

For me it is the constructors for advanced records that is broken... But that does not explain the double finalize.. (and why I don't see it??).
The constructor for records is nothing other than syntactic sugar around a static class method. I just like it because it has the implicit self scope in it, which is just cleaner then always writing: Result.XXX = ...

The problem I see is, what if you don't use the constructor directly, or as an iterator, but you have an iterator that includes a managed type. This might also trigger the bug, as the management handling routines are recursive to all the subfields.
So while I think you can avoid it when thinking about it with the records directly, every time this record is used in some other record, the user of this record also must think about not using it for iterators and in record constructors. And this is where it gets tricky, especially when, like in my project, the goal is to create a library that others, probably without the knowledge that I learned about this during experimentation, want to use these types

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: freeing objects
« Reply #14 on: February 15, 2023, 07:49:20 pm »
In the original example inherited Create should be done before other operations  in class A and B’s create method.

 

TinyPortal © 2005-2018