Lazarus

Programming => LCL => Topic started by: mtanner on February 21, 2022, 03:31:19 pm

Title: TStringList with objects
Post by: mtanner on February 21, 2022, 03:31:19 pm
Not sure if this is the right section of the forum.
I am using a TStringList, adding lines of text, and some lines have an object associated with them, with Add or AddObject. This works fine, and I can retrieve individual lines of text ok.

But I cannot figure out how to retrieve an object associated with a line. I'be looked at the code and all the documentation that I can find. Could someone please just respond with a single line of code that retrieves the object assocuated with a specifie line in the TStringList?

I have tried things like
mylist.objects;
mylist.object;
mylist.GetObject(I);
mylist.GetObject;

but all of these are apparently wrong. I've seen lots of stuff about using TStringList, intriductory to very complex, but cannot find the answer to the above seemingly trivial question.
FI
Title: Re: TStringList with objects
Post by: Zvoni on February 21, 2022, 03:38:39 pm
??
Untested! https://www.freepascal.org/docs-html/rtl/classes/tstrings.objects.html
Code: Pascal  [Select][+][-]
  1. Var
  2.    MyClass : TMyClass;  //Or TObject
  3. Begin
  4.    MyClass:=TMyClass(MyList.Objects[SomeValidIndex]);  //If it's TObject above, no need for Casting
  5. End;
  6.  
Title: Re: TStringList with objects
Post by: Sieben on February 21, 2022, 03:39:21 pm
The corresponding object of

Code: Pascal  [Select][+][-]
  1. mylist.Strings[i]

is

Code: Pascal  [Select][+][-]
  1. mylist.Objects[i]

It returns a TObject, so if you want to access the class you initially put there you have to use a typecast as Zvoni already mentioned. And as you wrote that only some lines are associated with an object, you'd better test like this:

Code: Pascal  [Select][+][-]
  1. if Assigned(myList.Objects[i]) then  // equivalent to: if (myList.Objects[i] <> nil) then
  2.   myclass := TMyClass(myList.Objects[i]);

or, even better:

Code: Pascal  [Select][+][-]
  1. if (myList.Objects[i] is TMyClass) then
  2.   myclass := TMyClass(myList.Objects[i]);

The 'is'-test tests for nil as well. And you know for sure that it is actually of the class you want and expect.
Title: Re: TStringList with objects
Post by: mtanner on February 21, 2022, 04:06:48 pm
Thank you.
Title: Re: TStringList with objects
Post by: Handoko on February 21, 2022, 04:33:29 pm
I'm late. But I hope this demo can be helpful maybe for others:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     CheckBox1: TCheckBox;
  17.     Label1: TLabel;
  18.     Memo1: TMemo;
  19.     RadioButton1: TRadioButton;
  20.     procedure Button1Click(Sender: TObject);
  21.     procedure CheckBox1Click(Sender: TObject);
  22.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  23.     procedure FormCreate(Sender: TObject);
  24.     procedure Memo1Change(Sender: TObject);
  25.     procedure RadioButton1Click(Sender: TObject);
  26.   private
  27.     Info: TStringList;
  28.     procedure SetLink;
  29.     function WhichLine(TheObject: TObject) : string;
  30.   end;
  31.  
  32. var
  33.   Form1: TForm1;
  34.  
  35. implementation
  36.  
  37. {$R *.lfm}
  38.  
  39. { TForm1 }
  40.  
  41. procedure TForm1.FormCreate(Sender: TObject);
  42. begin
  43.   Info := TStringList.Create;
  44.   SetLink;
  45. end;
  46.  
  47. procedure TForm1.Memo1Change(Sender: TObject);
  48. begin
  49.   SetLink;
  50. end;
  51.  
  52. procedure TForm1.RadioButton1Click(Sender: TObject);
  53. begin
  54.   ShowMessage(WhichLine(CheckBox1));
  55. end;
  56.  
  57. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  58. begin
  59.   Info.Free;
  60. end;
  61.  
  62. procedure TForm1.Button1Click(Sender: TObject);
  63. begin
  64.   ShowMessage(WhichLine(Button1));
  65. end;
  66.  
  67. procedure TForm1.CheckBox1Click(Sender: TObject);
  68. begin
  69.   ShowMessage(WhichLine(CheckBox1)); // Sorry, does not work on GTK2
  70. end;
  71.  
  72. procedure TForm1.SetLink;
  73. var
  74.   i: Integer;
  75. begin
  76.  
  77.   Info.Clear;
  78.   Info.Assign(Memo1.Lines);
  79.  
  80.   if Info.Count > 0 then
  81.     Info.Objects[0] := Button1;
  82.   if Info.Count > 1 then
  83.     Info.Objects[1] := CheckBox1;
  84.   if Info.Count > 2 then
  85.     Info.Objects[2] := RadioButton1;
  86.   if Info.Count > 3 then
  87.     Info.Objects[3] := Label1;
  88.  
  89.   Button1.Hint      := '';
  90.   CheckBox1.Hint    := '';
  91.   RadioButton1.Hint := '';
  92.   Label1.Hint       := '';
  93.   for i := 0 to Info.Count-1 do
  94.     (Info.Objects[i] as TControl).Hint := Info[i];
  95.  
  96. end;
  97.  
  98. function TForm1.WhichLine(TheObject: TObject): string;
  99. var
  100.   Index: Integer;
  101.   i:     Integer;
  102. begin
  103.   Index := -1;
  104.   for i := 0 to Info.Count-1 do
  105.     if Info.Objects[i] = TheObject then
  106.     begin
  107.       Index := i;
  108.       Break;
  109.     end;
  110.   if Index < 0 then Exit;
  111.   Result := 'Index of the line associated is = ' + Index.ToString;
  112. end;
  113.  
  114. end.
Title: Re: TStringList with objects
Post by: jcmontherock on February 21, 2022, 04:47:00 pm
Add and retrieve objects in TStringList work fine. The only limitation is with "SaveToFile" and "LoadFromFile": object are not saved to file.
Title: Re: TStringList with objects
Post by: BeniBela on February 21, 2022, 07:33:19 pm
TStringList can do everything a little, but nothing well

So it is always a bad idea to use TStringList anywhere
Title: Re: TStringList with objects
Post by: howardpc on February 21, 2022, 07:37:01 pm
What class(es) would you suggest as an improvement on TStringList, and how are they better?
Title: Re: TStringList with objects
Post by: Zvoni on February 22, 2022, 08:28:40 am
TStringList can do everything a little, but nothing well

So it is always a bad idea to use TStringList anywhere
What are you using TStringList for?
Title: Re: TStringList with objects
Post by: PascalDragon on February 22, 2022, 09:06:19 am
TStringList can do everything a little, but nothing well

So it is always a bad idea to use TStringList anywhere

Such generalized statements aren't helpful. So please provide specific reasons why you think that.
Title: Re: TStringList with objects
Post by: egsuh on February 22, 2022, 09:46:03 am
If you are looking for an object connected to a specific string, you may need following.


myobject := mylist.objects[MyList.IndexOf('Your specific string')];
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 22, 2022, 09:53:32 am
I like TStringList, it's the object I use most, in general. It can do many useful things.

Yes, I could make a custom class for each implementation, that is probably a bit more efficient and to the point, but it takes more time and the result will be the same.
Title: Re: TStringList with objects
Post by: Thaddy on February 22, 2022, 10:35:15 am
What class(es) would you suggest as an improvement on TStringList, and how are they better?
Any generic map or dictionary? Because they are type safe for all elements. But you know all that....
Title: Re: TStringList with objects
Post by: jcmontherock on February 22, 2022, 11:32:41 am
I found "AddObject", "InsertObject", but I didn't find "DeleteObject". Do you have any idea for deleting an object ? Maybe "Reduce" ?
Title: Re: TStringList with objects
Post by: Zvoni on February 22, 2022, 12:07:55 pm
I found "AddObject", "InsertObject", but I didn't find "DeleteObject". Do you have any idea for deleting an object ? Maybe "Reduce" ?
???
Not tested.....
Code: Pascal  [Select][+][-]
  1.   MyList.Objects[SomeValidIndex].Free;
  2.   //FreeAndNil(MyList.Objects[SomeValidIndex]);
  3.  
Title: Re: TStringList with objects
Post by: Sieben on February 22, 2022, 12:47:32 pm
Quote
I found "AddObject", "InsertObject", but I didn't find "DeleteObject".

If you just want to remove it from the list:

Code: Pascal  [Select][+][-]
  1. mylist.Objects[i] := nil;
Title: Re: TStringList with objects
Post by: PascalDragon on February 22, 2022, 01:34:29 pm
I found "AddObject", "InsertObject", but I didn't find "DeleteObject". Do you have any idea for deleting an object ? Maybe "Reduce" ?
???
Not tested.....
Code: Pascal  [Select][+][-]
  1.   MyList.Objects[SomeValidIndex].Free;
  2.   //FreeAndNil(MyList.Objects[SomeValidIndex]);
  3.  

You should also set it to Nil cause otherwise there will be problems if TStringList.OwnsObjects is set to True.

Quote
I found "AddObject", "InsertObject", but I didn't find "DeleteObject".

If you just want to remove it from the list:

Code: Pascal  [Select][+][-]
  1. mylist.Objects[i] := nil;

Just in case this isn't clear: this will not free the object instance. So if you set it to Nil without having a reference to that object instance somewhere else you'll have a memory leak.
Title: Re: TStringList with objects
Post by: Sieben on February 22, 2022, 01:50:32 pm
Quote
Just in case this isn't clear: this will not free the object instance.

That's why I wrote 'remove it from the list'. But you're right of course, always better to point it out.
Title: Re: TStringList with objects
Post by: engkin on February 22, 2022, 02:32:55 pm
What class(es) would you suggest as an improvement on TStringList, and how are they better?

Probably class TFPHashObjectList from unit Contnrs.
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 22, 2022, 02:49:05 pm
What class(es) would you suggest as an improvement on TStringList, and how are they better?
Any generic map or dictionary? Because they are type safe for all elements. But you know all that....
A common problem with descendants of classes like TFPGMap (TFPSList) is, that they have one or more public constructors and/or methods you really don't want any interfacing code to use. (Anything that handles plain pointers, for a start.) Not a problem if you're the sole developer, but not so good in a team or interface. And writing your own custom container class from scratch every time gets old fast.
Title: Re: TStringList with objects
Post by: jcmontherock on February 22, 2022, 06:32:29 pm
FreeAndNil(MyList.Objects[SomeValidIndex]);  gives a compile error   ;O(
Title: Re: TStringList with objects
Post by: Handoko on February 22, 2022, 08:07:27 pm
You did it wrong. See the line #69:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Label1: TLabel;
  17.     procedure Button1Click(Sender: TObject);
  18.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  19.     procedure FormCreate(Sender: TObject);
  20.   private
  21.     FStringList: TStringList;
  22.     function GenerateRandomItem: TShape;
  23.     function GenerateText(aShape: TShape): string;
  24.     procedure ShowInformation;
  25.   end;
  26.  
  27. var
  28.   Form1: TForm1;
  29.  
  30. implementation
  31.  
  32. {$R *.lfm}
  33.  
  34. { TForm1 }
  35.  
  36. procedure TForm1.Button1Click(Sender: TObject);
  37. const
  38.   ClickCount: Integer = 0;
  39. var
  40.   Something: TShape;
  41.   S:         string;
  42.   i:         Integer;
  43. begin
  44.  
  45.   // === First click
  46.   // Create some items and store the information to FStringList
  47.   if ClickCount = 0 then
  48.   begin
  49.     for i := 0 to 15 do
  50.     begin
  51.       Something := GenerateRandomItem;
  52.       S         := GenerateText(Something);
  53.       Something.Hint := '#' + i.ToString + ' - ' + S;
  54.       FStringList.Add('Item #' + i.ToString + ' is a ' + LowerCase(S) + '.');
  55.       FStringList.Objects[i] := Something;
  56.     end;
  57.     ShowInformation;
  58.     Button1.Caption := 'Click me AGAIN !';
  59.     Inc(ClickCount);
  60.     Exit;
  61.   end;
  62.  
  63.   // === Second click
  64.   // Delete all the items in the FStringList
  65.   if ClickCount = 1 then
  66.   begin
  67.     repeat
  68.       Something := (FStringList.Objects[0] as TShape);
  69.       FreeAndNil(Something);
  70.       FStringList.Delete(0);
  71.     until FStringList.Count <= 0;
  72.     ShowInformation;
  73.     Button1.Enabled := False;
  74.   end;
  75.  
  76. end;
  77.  
  78. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  79. begin
  80.   FStringList.Free;
  81. end;
  82.  
  83. procedure TForm1.FormCreate(Sender: TObject);
  84. begin
  85.   FStringList := TStringList.Create;
  86. end;
  87.  
  88. function TForm1.GenerateRandomItem: TShape;
  89. var
  90.   NewShape: TShape;
  91. begin
  92.   NewShape := TShape.Create(Self);
  93.   with NewShape do
  94.   begin
  95.     Parent   := Self;
  96.     Width    := 50;
  97.     Height   := 50;
  98.     Left     := Random(Self.Width  - 50);
  99.     Top      := Random(Self.Height - 90);
  100.     ShowHint := True;
  101.     case Random(3) of
  102.       0: Shape := stRectangle;
  103.       1: Shape := stCircle;
  104.       2: Shape := stTriangle;
  105.     end;
  106.     case Random(3) of
  107.       0: Brush.Color := clYellow;
  108.       1: Brush.Color := clGreen;
  109.       2: Brush.Color := clRed;
  110.     end;
  111.   end;
  112.   Result := NewShape;
  113. end;
  114.  
  115. function TForm1.GenerateText(aShape: TShape): string;
  116. var
  117.   S: string;
  118. begin
  119.   S := '';
  120.   case aShape.Brush.Color of
  121.     clYellow: S := 'Yellow';
  122.     clGreen:  S := 'Green';
  123.     clRed:    S := 'Red';
  124.   end;
  125.   case aShape.Shape of
  126.     stRectangle: S := S + ' Retangle';
  127.     stCircle:    S := S + ' Circle';
  128.     stTriangle:  S := S + ' Triangle';
  129.   end;
  130.   Result := S;
  131. end;
  132.  
  133. procedure TForm1.ShowInformation;
  134. var
  135.   S: string;
  136.   i: integer;
  137. begin
  138.   S := '';
  139.   for i := 0 to FStringList.Count-1 do
  140.     S := S + FStringList[i] + LineEnding;
  141.   Label1.Caption := S;
  142.   Label1.Visible := True;
  143.   Height := Label1.Height + 250;
  144. end;
  145.  
  146. end.
Title: Re: TStringList with objects
Post by: wp on February 22, 2022, 09:46:35 pm
FreeAndNil(MyList.Objects[SomeValidIndex]);  gives a compile error   ;O(
Why FreeAndNil at all? With setting a pointer variable to nil you signal to other pieces of your code that this object does not exist any more. But this is not necessary here at all since you free the entire list and thus it is impossible to access the individual pointers to these objects any more.  Do not confuse the pointers stored in the list with other variables pointing to the same object instance - they will not be nil'ed anyway even if you call FreeAndNil for MyList.Objects[..].

Simply .Free the object:
Code: Pascal  [Select][+][-]
  1.   MyList.Objects[SomeValidIndex].Free;
Title: Re: TStringList with objects
Post by: jcmontherock on February 23, 2022, 04:10:12 pm
Handoko, you have right with your point:

FreeAndNil(MyList.Objects[SomeValidIndex]);  gives a compile error: Can't take the address of constant expressions.

MyObject := TMyClass(MyList.Objects[SomeValidIndex]);
FreeAndNil(MyObject);                                      gives no error
Title: Re: TStringList with objects
Post by: Handoko on February 23, 2022, 04:18:07 pm
Yes, it is not only gives no error it also runs perfectly without error.

But as @wp said, maybe you don't need to use FreeAndNil. I'm not interested into the debate of using FreeAndNil or not, but I only used it once in a program I wrote, and I really had the reason why FreeAndNil was needed. For all the other programs I wrote, using Free only, seem to run without any issue. So, I can sure to say 99% Free only is enough.
Title: Re: TStringList with objects
Post by: BeniBela on February 23, 2022, 08:43:10 pm
What class(es) would you suggest as an improvement on TStringList, and how are they better?

Classes are also a bad idea. They gives too much trouble with Free




If you mostly use the Text property, I would just suggest a  string.

For a list of strings, I would suggest array of string.

For a list of strings and objects, I would suggest array of record a: string; b: TYourObject; end  . And TYourObject should be an object or record, not a class.

For a sorted list of strings and objects used as map, I would suggest TDictionary

Title: Re: TStringList with objects
Post by: MarkMLl on February 23, 2022, 10:24:44 pm
I'm afraid that I'm very sceptical that this is good advice for a newcomer.

For a list of strings and objects, I would suggest array of record a: string; b: TYourObject; end  . And TYourObject should be an object or record, not a class.

OP has already said that he's using a TStringList, and is asking for advice in that context.

Muddying the water by introducing the obsolete Turbo Pascal object (if that is your intention), or by overlooking the fact that OP already knows that he needs an /instance/ of a class rather than the class itself, is not helpful.

MarkMLl
Title: Re: TStringList with objects
Post by: Sieben on February 24, 2022, 12:17:13 am
But as @wp said, maybe you don't need to use FreeAndNil. I'm not interested into the debate of using FreeAndNil or not, but I only used it once in a program I wrote, and I really had the reason why FreeAndNil was needed. For all the other programs I wrote, using Free only, seem to run without any issue. So, I can sure to say 99% Free only is enough.

Most of the time because the vars just go out of scope, joining the quire invisible. But if they don't you'd better nil 'em.
Title: Re: TStringList with objects
Post by: PascalDragon on February 24, 2022, 09:09:03 am
FreeAndNil(MyList.Objects[SomeValidIndex]);  gives a compile error   ;O(
Why FreeAndNil at all? With setting a pointer variable to nil you signal to other pieces of your code that this object does not exist any more. But this is not necessary here at all since you free the entire list and thus it is impossible to access the individual pointers to these objects any more.  Do not confuse the pointers stored in the list with other variables pointing to the same object instance - they will not be nil'ed anyway even if you call FreeAndNil for MyList.Objects[..].

Simply .Free the object:
Code: Pascal  [Select][+][-]
  1.   MyList.Objects[SomeValidIndex].Free;

No, you should Nil it, cause otherwise there'll be a crash if OwnsObjects is set to True. Thus the correct one in this case is:

Code: Pascal  [Select][+][-]
  1. MyList.Objects[SomeValidIndex].Free;
  2. MyList.Objects[SomeValidIndex] := Nil;

Handoko, you have right with your point:

FreeAndNil(MyList.Objects[SomeValidIndex]);  gives a compile error: Can't take the address of constant expressions.

MyObject := TMyClass(MyList.Objects[SomeValidIndex]);
FreeAndNil(MyObject);                                      gives no error

This is rather useless. If you use a variable simply to use FreeAndNil then you have not understood what FreeAndNil does: MyObject will be Nil after that call, but MyList.Objects[SomeValidIndex] will not (and neither will any other reference that points to that object instance).

For a list of strings and objects, I would suggest array of record a: string; b: TYourObject; end  . And TYourObject should be an object or record, not a class.

Which would require users to write functions like IndexOf by themselves possibly introducing errors along the way. The container types exist for a reason, namely that one doesn't need to reinvent the wheel again and that one can concentrate on what is important: one's own code.

What class(es) would you suggest as an improvement on TStringList, and how are they better?
Any generic map or dictionary? Because they are type safe for all elements. But you know all that....
A common problem with descendants of classes like TFPGMap (TFPSList) is, that they have one or more public constructors and/or methods you really don't want any interfacing code to use. (Anything that handles plain pointers, for a start.) Not a problem if you're the sole developer, but not so good in a team or interface. And writing your own custom container class from scratch every time gets old fast.

Then use Generics.Collections.TDictionary if you're worried about that.

But as @wp said, maybe you don't need to use FreeAndNil. I'm not interested into the debate of using FreeAndNil or not, but I only used it once in a program I wrote, and I really had the reason why FreeAndNil was needed. For all the other programs I wrote, using Free only, seem to run without any issue. So, I can sure to say 99% Free only is enough.

Most of the time because the vars just go out of scope, joining the quire invisible. But if they don't you'd better nil 'em.

This is only true for managed types and record and object instances that are located on the stack (or are global variables). Class instances and any other manual allocations need to be freed explicitely as well.
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 24, 2022, 09:09:28 am
I'm afraid that I'm very sceptical that this is good advice for a newcomer.

For a list of strings and objects, I would suggest array of record a: string; b: TYourObject; end  . And TYourObject should be an object or record, not a class.

OP has already said that he's using a TStringList, and is asking for advice in that context.

Muddying the water by introducing the obsolete Turbo Pascal object (if that is your intention), or by overlooking the fact that OP already knows that he needs an /instance/ of a class rather than the class itself, is not helpful.

MarkMLl

Indeed. I would even go as far to advise only making and using classes, simply to keep it all simple and consistent. Learn to do one thing well.
Title: Re: TStringList with objects
Post by: MarkMLl on February 24, 2022, 09:42:13 am
Indeed. I would even go as far to advise only making and using classes, simply to keep it all simple and consistent. Learn to do one thing well.

Making and /instantiating/ classes.

Sorry, I'm not trying to be pedantic but there's far too much confusion in the terminology: the stringlist that OP's using is an instance of a class, and the object (optionally) associated with each string is an instance of a class.

The important point there is that TStringList.Objects contains instances of classes. In parallel with "class" there is also the deprecated "object", which is distinct and best avoided... at least until one has read, marked and digested the documentation together with the extended discussion at https://forum.lazarus.freepascal.org/index.php/topic,57040.0.html

MarkMLl
Title: Re: TStringList with objects
Post by: Sieben on February 24, 2022, 11:16:16 am
But as @wp said, maybe you don't need to use FreeAndNil. I'm not interested into the debate of using FreeAndNil or not, but I only used it once in a program I wrote, and I really had the reason why FreeAndNil was needed. For all the other programs I wrote, using Free only, seem to run without any issue. So, I can sure to say 99% Free only is enough.

Most of the time because the vars just go out of scope, joining the quire invisible. But if they don't you'd better nil 'em.

This is only true for managed types and record and object instances that are located on the stack (or are global variables). Class instances and any other manual allocations need to be freed explicitely as well.

If you re-read the quotes you made this is exactly what we were talking about: of course you will have to free them, but you don't necessarily also have to nil the vars that used to hold (or point to) the class instances when they go out of scope anyway. I just added that you should as well nil when they don't, being a field in another class for example.
Title: Re: TStringList with objects
Post by: Thaddy on February 24, 2022, 01:10:44 pm
There are two anti patterns here:
1. Misusing a stringlist to store objects (a relic from the past)
2. The Free and Nil anti pattern.

Shame on all that are still under the illusion that both of those are sane programming. >:D >:D
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 24, 2022, 02:20:53 pm
I do use TFPGMap and TFPGList, quite often. But if it is for a framework or template, I make all the constructors and methods I don't want used private. I will use TDictionary in the future.

And I do make custom classes all the time. Functions are for generic stuff only and not exposed. But inside those functions and classes I use whatever is easy and works.
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 24, 2022, 04:26:14 pm
Btw, FreeAndNil is sometimes needed, because Free leaves a dangling pointer, without an obvious way to check the state that doesn't generate an exception. There are more reasons to free something than only it going out of scope. That's also why you have to nil the pointer if another object is the owner. And you cannot use FreeAndNill in that case, because you get the object itself, not the pointer. But in that case, you should remove the whole item (the string, for a TStringList) and let the owner clean it up. If you want to replace the object, first remove the previous one and then add the new one.

Then again, "fixing" Free would require either reference counting or limited garbage collection, so I do understand the problem. Still, that would prevent a lot of bugs.
Title: Re: TStringList with objects
Post by: MarkMLl on February 24, 2022, 04:38:18 pm
Also FreeAndNil() IIRC checks that its parameter is not nil before attempting to free it, which is a Good Thing.

However trying to remain focused on OP's use of TStringList, I think that not being able to do an indivisible free-and-nil via .Objects[] is an issue.

MarkMLl
Title: Re: TStringList with objects
Post by: jcmontherock on February 24, 2022, 05:03:26 pm
Code: Pascal  [Select][+][-]
  1. MyList.Objects[SomeValidIndex].Free;
  2.  

What the difference between "Free" and "FreeInstance" ? Does it suppress the allocated memory for Objects ?
Title: Re: TStringList with objects
Post by: Thaddy on February 24, 2022, 06:05:53 pm
Btw, FreeAndNil is sometimes needed, because Free leaves a dangling pointer,
That is the basic mistake.
Dangling pointers are ALWAYS programmer error. Always.
Title: Re: TStringList with objects
Post by: howardpc on February 24, 2022, 10:03:32 pm
FreeAndNil is recommended in the FPC documentation here (https://www.freepascal.org/docs-html/current/rtl/system/tobject.destroy.html):
Quote
It is bad programming practice to call Destroy directly. It is better to call the Free (https://www.freepascal.org/docs-html/current/rtl/system/tobject.free.html) method, because that one will check first if Self is different from Nil.
To clean up an instance and reset the reference to the instance, it is best to use the FreeAndNil (https://www.freepascal.org/docs-html/current/rtl/sysutils/freeandnil.html) function


Title: Re: TStringList with objects
Post by: Handoko on February 24, 2022, 10:22:45 pm
Pardon me if my English is limited. But these are what I get from that quote:
- Calling Free is better than call Destroy directly.
- FreeAndNil is for cleaning up instance and reset reference to the instance.
- None of them said FreeAndNil is better than Free only.

Challenge: Provide Me The Compile-able Demos

As I am the less experienced here, I am not qualify for arguing. But can anyone please provide real demos showing FreeAndNil is really needed? And if we change that FreeAndNil then the program will cause error.

I do not write large-scale-sized programs but I have been writing plenty of small and thousand lines of code programs. So far I only got 1 case why FreeAndNil is really required. But before I show you the code, I would be interested to see if anyone can provide me some.

FreeAndNil or not have been discussed for years but still have no a mutual agreement. So instead of just arguing, show me the code.

I will wait 1 week before I provide mine. But if till then still no one is able to provide a real demo then I will keep believing 99% Free only is enough.
Title: Re: TStringList with objects
Post by: korba812 on February 24, 2022, 11:54:17 pm
In most cases it makes no difference what method you use. It is your choice and your preferences. Personally, I use FreeAndNil because problems like "use after free" can be easily detected - an AV exception will be raised.
Title: Re: TStringList with objects
Post by: 440bx on February 25, 2022, 12:19:50 am
But can anyone please provide real demos showing FreeAndNil is really needed? And if we change that FreeAndNil then the program will cause error.

FreeAndNil or not have been discussed for years but still have no a mutual agreement.

But if till then still no one is able to provide a real demo then I will keep believing 99% Free only is enough.
I normally stay away from discussions about OOP but, in this particular case, it's not just about OOP.

The "clean" (for lack of a better word) way is to use FreeAndNil.  The reason is very simple, a class is a pointer to a heap memory block.  Once the memory block has been freed, it should not be accessed again.  If the class/pointer has not been nil-ed then it is possible to dereference the pointer after the memory has been freed, which is a programming error but, it often won't be visible immediately.  if the pointer is nil-ed then any subsequent de-reference will cause an access violation (as it should) revealing the programming error right then and there.

As far as compile-able code, the subject "FreeAndNil" in the FPC Wiki, has an example that shows the problem with just Free.  It's also worth noting that there is code out there that tests if some class is Assigned before it proceeds to do something with it.  If the class/pointer is freed and not nil-ed, Assigned will return TRUE causing the rest of the code to access memory that is completely unrelated to the tested class.

Basically, whether in OOP or any other kind of programming, if a variable points to a dynamically allocated memory block and, the memory block is freed then the variable should be set to nil because whatever value it had got invalidated when the block got freed.

Think of it as variable "re-initialization".  The variable should be nil before a memory block is allocated to hold the class, it should be reset to nil when the block is freed.  That way the value/state of the variable mirrors the state of what it represents (as it should.)

Title: Re: TStringList with objects
Post by: Handoko on February 25, 2022, 04:55:13 am
It is your choice and your preferences.

Very true. It is more about personal preference. Maybe I am sensitive, it seems to me the usage of FreeAndNil has been 'glorified'. People keep saying it's better. So, I might need to use it more too in the future but I need solid proofs that it is really better than simply Free.

As far as compile-able code, the subject "FreeAndNil" in the FPC Wiki, has an example that shows the problem with just Free.  It's also worth noting that there is code out there that tests if some class is Assigned before it proceeds to do something with it. If the class/pointer is freed and not nil-ed, Assigned will return TRUE causing the rest of the code to access memory that is completely unrelated to the tested class.

Yes, that was I what did. And my code won't run correctly without using FreeAndNil.  I didn't write the code to proof FreeAndNil, I really 'accidentally' encountered such case. But only once so far. So I later introspected, I thought maybe the problem was the code design.

I don't mean we shouldn't use FreeAndNil. I just want to proof 99% Free only is enough. Because the cases that FreeAndNil is required are really rare.
Title: Re: TStringList with objects
Post by: balazsszekely on February 25, 2022, 06:47:12 am
@Handoko
Quote
Yes, that was I what did. And my code won't run correctly without using FreeAndNil.  I didn't write the code to proof FreeAndNil, I really 'accidentally' encountered such case. But only once so far. So I later introspected, I thought maybe the problem was the code design.

I don't mean we shouldn't use FreeAndNil. I just want to proof 99% Free only is enough. Because the cases that FreeAndNil is required are really rare.
Please teet the following scenario, but first make sure that Form2 is not in the Auto-Create Forms:
Code: Pascal  [Select][+][-]
  1. uses Unit2;
  2.  
  3. procedure TForm1.Button1Click(Sender: TObject);
  4. begin
  5.   Form2 := TForm2.Create(nil);
  6.   try
  7.     Form2.ShowModal;
  8.   finally
  9.     Form2.Free;
  10.     //Form2 := nil;
  11.   end;
  12.  
  13.   if Form2 <> nil then
  14.     Form2.Caption := 'exception here without Form2 := nil';
  15. end;

Comment/Uncomment  line: Form2 := nil; and see the difference.
Title: Re: TStringList with objects
Post by: 440bx on February 25, 2022, 07:35:04 am
I don't mean we shouldn't use FreeAndNil. I just want to proof 99% Free only is enough. Because the cases that FreeAndNil is required are really rare.
It's important to realize that it's not a matter of how rare something is needed.  It's simply good programming and that should be _continuous_.

For instance, declaring the initial value of a variable in a function is actually rarely needed because the common case is that the variables will be assigned some value in the code (at least they should) but, by initializing them in the declaration you know  what their initial value is, instead of being whatever they overlay on the stack, because of that, if something isn't working quite right, it will usually be much easier to find and also, the behavior will almost always be reproducible because the variable are always initialized to a specific value.  Whereas if one or more variables are left uninitialized, they'll have whatever values happened to be on the stack causing the behavior to often vary, making the bug harder to find.

The value of a pointer should reflect the state of the block pointed to.  Whenever a block is freed, its pointer should be set to nil to reflect the fact that it no longer points to a valid block of memory.  It's actually somewhat rare to actually _need_ to reset the variable to nil after freeing the block but, it's the only genuinely correct thing to do because, if not, the value in the variable no longer reflects the state of the program and, sometimes that comes back to bite the programmer in undesirable areas ;)

Title: Re: TStringList with objects
Post by: MarkMLl on February 25, 2022, 09:13:08 am
The "clean" (for lack of a better word) way is to use FreeAndNil.  The reason is very simple, a class is a pointer to a heap memory block.  Once the memory block has been freed, it should not be accessed again.  If the class/pointer has not been nil-ed then it is possible to dereference the pointer after the memory has been freed, which is a programming error but, it often won't be visible immediately.  if the pointer is nil-ed then any subsequent de-reference will cause an access violation (as it should) revealing the programming error right then and there.

I'd be happier if it were documented as being indivisible.

Since it's not, there's the potential for an avoidable race condition when the pointer is first inspected.

MarkMLl
Title: Re: TStringList with objects
Post by: wp on February 25, 2022, 10:33:53 am
I don't condemn FreeAndNil, but I try to avoid it as much as possible because it can hide bugs. Everybody thinks exceptions are a bad thing - no they are here to help you to find bugs. Checking freed pointers for nil is a habit which prevents exceptions - but along with automatic nilling deallocated pointers this prevents you from seeing the bug.

Here is an example:

Suppose a form with a TStringList, FList.

There is a button which creates the stringlist and reads a data file into it. But you make a mistake and destroy the StringList immediately afterwards, and because you have been told to use FreeAndNil you are used to typing FreeAndNil for destroying it:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.   FList := TStringList.Create;
  4.   FList.LoadFromFile('data.txt');
  5.   FreeAndNil(FList);    // better: FList.Free;
  6. end;

There is another button  which is supposed to analyze the contents of the stringlist, maybe for duplicate lines. Because for some reasons the click handler of this button performs some other things the button can be clicked before the FList has been created, and you decide to simply check FList for not being nil:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. begin
  3.   DoSomethingElse;
  4.   if FList <> nil then
  5.     FindDuplicateLines(FList);
  6. end;

Running this program you will notice no duplicates lines - never. Unless you know for sure that the test file does contain duplicate lines you will not notice that the analysis is not correct.

The problem, of course, is the destruction of the stringlist after creating it (Button1Click). If you were not used to typing FreeAndNil without thinking you'd have typed "FList.Free". And this would have caused an exception in your tests with ANY file.

So, my message is: Never type FreeAndNil without thinking. I agree with Handoko above that calling .Free instead of FreeAndNil is better in most cases.
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 25, 2022, 10:35:53 am
The main problem with Free is multiple pointers to the same object. Yes, if you only have one pointer and the object goes out of scope, Free is fine. But if you don't like globals, you pass pointers around. You probably have a class somewhere that is the owner of that object, like that TStringList. You need to have one owner for each object instance, to decide who is going to Free it.

So, we have an object, with an owner, that hands out pointers to that object. Not to the pointer to that object it manages, but directly. If one of the parties that have one of those pointers free the object, all those pointers become invalid. FreeAndNil isn't going to help there, because it will only nil the pointer that is used to Free the object. And the only way to test if your pointer is still valid is accessing the object: if an access exception is raised, it wasn't. Lots of try .. except.

So, how do you fix that? Thaddy would say: if someone else than the owner frees it, you're doing it wrong. And pointers passed are one-use only: don't store them or pass them along! Which is fair enough, and doable if you're the only programmer. But it's not very bullet-proof.

A better option would be: hand out pointers to the pointer. That way, there is only one master pointer, which is dereferenced by the other pointers. FreeAndNil that, and you're done. This is the idea behind smart pointers.

You could also store all references to the object, possibly in a linked list that is part of TObject. Like reverse reference counting. On Free, you start by nilling all those pointers. This would require changing the compiler.

And lastly, garbage collection. Keep a list of all pointers used and nil the ones that pointed to that object after each Free. This is the brute-force method.
Title: Re: TStringList with objects
Post by: 440bx on February 25, 2022, 11:09:08 am
I don't condemn FreeAndNil, but I try to avoid it as much as possible because it can hide bugs. Everybody thinks exceptions are a bad thing - no they are here to help you to find bugs. Checking freed pointers for nil is a habit which prevents exceptions - but along with automatic nilling deallocated pointers this prevents you from seeing the bug.
On the contrary, if the pointer is nil and it's dereferenced, there will be an exception (access violation.)  Thus clearly revealing the presence of a bug.

I agree that checking freed pointers before doing something with it is a bad habit.  The programmer should know _beforehand_ whether or not the pointer is nil.  IOW, checking that shouldn't be necessary in a well structured program.

Just for the record and, this shouldn't be news to anyone who has read my posts, I am one of those who is strongly against the casual use of exceptions.  Exceptions should be used for things that, as their name implies, are exceptional, not for trivial error handling resulting in cross stack frame gotos whose destination is, more often than not, unpredictable.

if a pointer isn't pointing to a valid memory block, it should be nil and, the reason is obvious, because it doesn't point to a valid memory block.

Title: Re: TStringList with objects
Post by: korba812 on February 25, 2022, 11:37:41 am
So, my message is: Never type FreeAndNil without thinking. I agree with Handoko above that calling .Free instead of FreeAndNil is better in most cases.

But by modifying your example you can show that FreeAndNil is useful:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.   FList := TStringList.Create;
  4.   FList.LoadFromFile('data.txt');
  5.   FList.Free;
  6. end;

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. begin
  3.   DoSomethingElse;
  4.   if FList <> nil then   // Do we still have a valid reference to the class? Nobody knows...
  5.     FindDuplicateLines(FList);
  6. end;
Title: Re: TStringList with objects
Post by: wp on February 25, 2022, 12:01:36 pm
But by modifying your example you can show that FreeAndNil is useful:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.   FList := TStringList.Create;
  4.   FList.LoadFromFile('data.txt');
  5.   FList.Free;
  6. end;

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. begin
  3.   DoSomethingElse;
  4.   if FList <> nil then   // Do we still have a valid reference to the class? Nobody knows...
  5.     FindDuplicateLines(FList);
  6. end;
It is always difficult it create useful and non-trivial examples... But as I wrote the example is constructed such that the Button1Click contains a bug, namely destroying the list immediately after loading the file which should be done somewhere else (certainly in FormDestroy or in some other button click). Assuming that it is a bug, nilling the instance by FreeAndNil makes it even worse because now the bug is hidden and hard to find, rather than raising an easily observed exception.
Title: Re: TStringList with objects
Post by: SymbolicFrank on February 25, 2022, 12:23:23 pm
It is interesting, that most languages with garbage collection assume that object instances exist until they go out of scope everywhere. So, what are you supposed to do if you want to replace that instance with another one? I do that often.

I mean, are you going to call Destroy and Create a new one with the same pointer? How do you make sure the other pointers don't keep pointing to the depreciated instance?
Title: Re: TStringList with objects
Post by: Handoko on February 25, 2022, 12:23:55 pm
@GetMem, @korba812

Thank you for providing those codes. But I think I need to rephrase what I said. The codes you showed are specially written to show bad programming practice. I would say those were the programmer fault. Why do still use that variable when you know for sure you have free it?

Okay, maybe this is what I need. Provide me the demos showing in what cases we need to reuse the free pointer when we know we already free them. Or better this way: Do you have any codes which have written correctly and they use FreeAndNil? And if you use the Find and Replace tool to replace those FreeAndNil to Free only, then the program stop working correctly.

I need real world cases. Not some imaginary cases or some lines specially written to show bad programming practices.

@440bx
For instance, declaring the initial value of a variable in a function is actually rarely needed because the common case is that the variables will be assigned some value in the code (at least they should) but, by initializing them in the declaration you know  what their initial value is, instead of being whatever they overlay on the stack, because of that, if something isn't working quite right, it will usually be much easier to find and also, the behavior will almost always be reproducible because the variable are always initialized to a specific value.  Whereas if one or more variables are left uninitialized, they'll have whatever values happened to be on the stack causing the behavior to often vary, making the bug harder to find.

Nice explanation. Using FreeAndNil is recommended because it is a good practice even it's rarely needed. Okay, I got your point. But I am more interested to see how frequent or rare is really needed.

So, my message is: Never type FreeAndNil without thinking.

+1
It is good to know why we use it instead of using it blindly without knowing the reason.

I remember some said misusing FreeAndNil may make the code harder to debug because it hides the real issue ... or maybe something like that. Can anyone explain in more detail?
Title: Re: TStringList with objects
Post by: Zoran on February 25, 2022, 12:37:14 pm

I remember some said misusing FreeAndNil may make the code harder to debug because it hides the real issue ... or maybe something like that. Can anyone explain in more detail?

https://community.embarcadero.com/blogs/entry/a-case-against-freeandnil-38910 (https://community.embarcadero.com/blogs/entry/a-case-against-freeandnil-38910)
https://blogs.embarcadero.com/a-case-when-freeandnil-is-your-enemy/ (https://blogs.embarcadero.com/a-case-when-freeandnil-is-your-enemy/)
https://wiert.me/2017/12/21/another-case-against-freeandnil/ (https://wiert.me/2017/12/21/another-case-against-freeandnil/)
https://www.delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore/ (https://www.delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore/)
https://stackoverflow.com/questions/3510179/why-should-i-use-free-and-not-freeandnil-in-a-destructor (https://stackoverflow.com/questions/3510179/why-should-i-use-free-and-not-freeandnil-in-a-destructor)
Title: Re: TStringList with objects
Post by: 440bx on February 25, 2022, 12:46:47 pm
But I am more interested to see how frequent or rare is really needed.
That will vary from program to program depending on how many blocks the program dynamically allocates, frees and re-uses.

I remember some said misusing FreeAndNil may make the code harder to debug because it hides the real issue ... or maybe something like that. Can anyone explain in more detail?
I am of the opposite opinion.  FreeAndNil should always make the code easier to debug because, if a pointer is nil then the reason why the de-reference is failing is completely obvious, whereas if the pointer has a value that points somewhere in memory, it will be much more difficult to figure out that it shouldn't be pointing there and figuring out what the values mean will be an exercise in futility because the pointer has no business pointing there.

A non-nil pointer that points to a freed block is a wolf disguised as a sheep.  (bad news for anyone who takes it for granted that sheep are harmless.) ;)


Title: Re: TStringList with objects
Post by: SymbolicFrank on February 25, 2022, 12:59:23 pm
This shows the problem:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.   Sysutils, Classes, Variants;
  5.  
  6. type
  7.   TRecord = class
  8.   public
  9.     Fields: array of Variant;
  10.   end;
  11.  
  12.   { TTable }
  13.  
  14.   TTable = class
  15.   private
  16.     MyRecord: TRecord;
  17.     function GetNext: TRecord;
  18.   public
  19.     constructor Create(Number: Cardinal);
  20.     procedure Next;
  21.     procedure Post;
  22.     property Current: TRecord read MyRecord;
  23.   end;
  24.  
  25. { TMyClass }
  26.  
  27. function TTable.GetNext: TRecord;
  28. begin
  29.   Result := nil;
  30.  
  31.   // Now go and fetch the next record
  32.   // Result := ...
  33. end;
  34.  
  35. constructor TTable.Create(Number: Cardinal);
  36. var
  37.   i: Integer;
  38. begin
  39.   Setlength(MyRecord.Fields, Number);
  40.   for i := 0 to Number - 1 do MyRecord.Fields[i] := NULL;
  41. end;
  42.  
  43. procedure TTable.Next;
  44. begin
  45.   MyRecord.Free;
  46.   MyRecord := GetNext;
  47. end;
  48.  
  49. procedure TTable.Post;
  50. begin
  51.   // Write Myrecord to the database
  52. end;
  53.  
  54. var
  55.   MyTable: TTable;
  56.   MyRecord: TRecord;
  57. begin
  58.   MyTable := TTable.Create(10);
  59.   MyRecord := MyTable.Current;
  60.   MyTable.Next;
  61.   MyRecord.Fields[1] := 'Bla';
  62.   MyTable.Post;
  63. end.

It's obvious here, but normally that is not so transparent.
Title: Re: TStringList with objects
Post by: balazsszekely on February 25, 2022, 01:01:51 pm
@Handoko
Quote
Thank you for providing those codes. But I think I need to rephrase what I said. The codes you showed are specially written to show bad programming practice. I would say those were the programmer fault. Why do still use that variable when you know for sure you have free it?
I agree that you can consider my example a bad programming practice, but I had to come up with something simple to illustrate the issue. In other, not so trivial cases it's very useful to set a particular object to nil.
Title: Re: TStringList with objects
Post by: wp on February 25, 2022, 01:37:21 pm
I remember some said misusing FreeAndNil may make the code harder to debug because it hides the real issue ... or maybe something like that. Can anyone explain in more detail?
Did you see my example in reply #46?
Title: Re: TStringList with objects
Post by: Thaddy on February 25, 2022, 01:38:55 pm
A TStringlist with objects is a MAP/DICTIONARY pattern as I wrote. So use a map/dictionary. Not the legacy code. That is core.
Title: Re: TStringList with objects
Post by: dseligo on February 25, 2022, 03:29:44 pm
There is another button  which is supposed to analyze the contents of the stringlist, maybe for duplicate lines. Because for some reasons the click handler of this button performs some other things the button can be clicked before the FList has been created, and you decide to simply check FList for not being nil:

Instead of using FList only if it is not nil, you should alert yourself or user that FList is not valid:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. begin
  3.   DoSomethingElse;
  4.   if FList = nil then
  5.     Raise Exception.Create('FList is nil and it shouldn''t be.');
  6.   FindDuplicateLines(FList);
  7. end;
Title: Re: TStringList with objects
Post by: PascalDragon on February 25, 2022, 03:44:07 pm
Code: Pascal  [Select][+][-]
  1. MyList.Objects[SomeValidIndex].Free;
  2.  

What the difference between "Free" and "FreeInstance" ? Does it suppress the allocated memory for Objects ?

TObject.FreeInstance is the low level part that really frees the memory and thus is the counterpart to TObject.NewInstance. It is called at the end of the destructor and calling it manually can be considered a programming error in nearly all cases.

TObject.Free checks whether Self is not Nil and then calls the destructor TObject.Destroy.

FreeAndNil is recommended in the FPC documentation here (https://www.freepascal.org/docs-html/current/rtl/system/tobject.destroy.html):
Quote
It is bad programming practice to call Destroy directly. It is better to call the Free (https://www.freepascal.org/docs-html/current/rtl/system/tobject.free.html) method, because that one will check first if Self is different from Nil.
To clean up an instance and reset the reference to the instance, it is best to use the FreeAndNil (https://www.freepascal.org/docs-html/current/rtl/sysutils/freeandnil.html) function

Read the quoted sentence again: To clean up an instance and reset the reference to the instance …. So this is about specific circumstances. Cause, well, it's one less line of code.

The "clean" (for lack of a better word) way is to use FreeAndNil.  The reason is very simple, a class is a pointer to a heap memory block.  Once the memory block has been freed, it should not be accessed again.  If the class/pointer has not been nil-ed then it is possible to dereference the pointer after the memory has been freed, which is a programming error but, it often won't be visible immediately.  if the pointer is nil-ed then any subsequent de-reference will cause an access violation (as it should) revealing the programming error right then and there.

I'd be happier if it were documented as being indivisible.

Since it's not, there's the potential for an avoidable race condition when the pointer is first inspected.

Documenting it as indivisible would not change that FreeAndNil simply is not indivisible. If you access the to be freed instance from multiple threads then you need to use synchronization.

I don't condemn FreeAndNil, but I try to avoid it as much as possible because it can hide bugs. Everybody thinks exceptions are a bad thing - no they are here to help you to find bugs. Checking freed pointers for nil is a habit which prevents exceptions - but along with automatic nilling deallocated pointers this prevents you from seeing the bug.

Not setting the reference to Nil can be just as problematic and even harder to debug, namely when the memory location the instance is pointing to is reused. Then there'll be either exceptions or subtle bugs. Take the following example and imagine it in a more complex setting:

Code: Pascal  [Select][+][-]
  1. program treuse;
  2.  
  3. {$mode objfpc}
  4.  
  5. type
  6.   TTest1 = class
  7.     f: LongInt;
  8.   end;
  9.  
  10.   TTest2 = class
  11.     f1, f2: UInt16;
  12.   end;
  13.  
  14. var
  15.   t1: TTest1;
  16.   t2: TTest2;
  17. begin
  18.   t1 := TTest1.Create;
  19.   t1.f := 42;
  20.   Writeln(HexStr(Pointer(t1)), ' ', HexStr(t1.f, SizeOf(LongInt) * 2));
  21.   t1.Free;
  22.   t2 := TTest2.Create;
  23.   t2.f1 := 1;
  24.   t2.f2 := 5;
  25.   Writeln(HexStr(Pointer(t1)), ' ', HexStr(t1.f, SizeOf(LongInt) * 2));
  26.   t2.Free;
  27. end.

Output:

Code: [Select]
PS D:\fpc\git> .\testoutput\treuse.exe
01595A00 0000002A
01595A00 00050001

As you can see the heap manager reused the memory location previously used by the TTest1 instance for the TTest2 instance. So there won't be any exception, but you'll get garbage when something continues to use the original reference.

And yes, I'm aware that FreeAndNil only Nils the one reference passed to it and doesn't solve everything, but using properties and such one more often then not does not "cache" some reference and then freeing the original reference might point you to the problem more quickly. I personally only use FreeAndNil when I know that the instance is accessible from somewhere else.

It is interesting, that most languages with garbage collection assume that object instances exist until they go out of scope everywhere. So, what are you supposed to do if you want to replace that instance with another one? I do that often.

I mean, are you going to call Destroy and Create a new one with the same pointer? How do you make sure the other pointers don't keep pointing to the depreciated instance?

Even in FPC that's dubious behaviour to actively abuse.

A TStringlist with objects is a MAP/DICTIONARY pattern as I wrote. So use a map/dictionary. Not the legacy code. That is core.

If one also uses the Name/Value functionality of TStringList or its encoding mechanisms or the Load*/Save*-methods in addition to the object instances then it might be better to use TStringList.
Title: Re: TStringList with objects
Post by: MarkMLl on February 25, 2022, 04:00:06 pm
I'd be happier if it were documented as being indivisible.

Since it's not, there's the potential for an avoidable race condition when the pointer is first inspected.

Documenting it as indivisible would not change that FreeAndNil simply is not indivisible. If you access the to be freed instance from multiple threads then you need to use synchronization.

No disrespect intended, but I assumed that would be read "/fixing/ and documenting..." :-)

The point is that as things stand the documentation says nothing either way, and I suspect that some are assuming that it is safer than it really is.

MarkMLl
Title: Re: TStringList with objects
Post by: jcmontherock on February 26, 2022, 07:07:57 pm
@Thaddy:

Quote
There are two anti patterns here:
1. Misusing a stringlist to store objects (a relic from the past)
2. The Free and Nil anti pattern.

AddObject with TStrings is not a relic of the past. It's used a lot of time in Lazarus, in "Package Manager" for example.
Title: Re: TStringList with objects
Post by: PascalDragon on February 27, 2022, 11:35:40 am
The point is that as things stand the documentation says nothing either way, and I suspect that some are assuming that it is safer than it really is.

In general it should be assumed that the RTL isn't threadsafe. The only exception are the internal management operations for strings, arrays and such. And types like TThreadList.
Title: Re: TStringList with objects
Post by: MarkMLl on February 27, 2022, 11:52:28 am
In general it should be assumed that the RTL isn't threadsafe. The only exception are the internal management operations for strings, arrays and such. And types like TThreadList.

Agreed. But I tend to think of indivisibility as being a stricter requirement than thread safety: in part that's due to working on larger systems (big SPARCs etc. which had copious SMP before PCs) but also due to some uncomfortable experiences where code which was strictly thread-safe resulted in the main GUI loop being reentered (resulting in an occasional hence elusive crash).

MarkMLl
Title: Re: TStringList with objects
Post by: jipété on December 01, 2022, 11:27:16 am
Hi,

some monthes later, back to beginning : How to retrieve data from TObjects in StringList

Starting with the 2nd and 3rd posts of that discussion (see below), I've tried to recover data from objects in stringlist, like that :
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. Var
  3.   MyClass : TObject;
  4.   AList : TStringList;
  5.   s : AnsiString;
  6. begin
  7.   AList := TStringList.Create;
  8.   try
  9.     AList.Add('Test');
  10.     AList.Objects[0] := TObject(integer(5));
  11.     MyClass := AList.Objects[{SomeValidIndex}0]; // If it's TObject above, no need for Casting
  12.     if MyClass = nil then Exit;
  13.   //s := MyClass; --> Error: Incompatible types: got "TObject" expected "AnsiString", so
  14.     s := AnsiString(MyClass);  // ********************* sigsegv here ***********************
  15.     ShowMessage('The value of Test is ' + s);
  16.   finally
  17.     AList.Free;
  18.   end;
  19. end;

Any idea ?
Thanks,

2nd post :
Code: Pascal  [Select][+][-]
  1. Var
  2.    MyClass : TMyClass;  //Or TObject
  3. Begin
  4.    MyClass:=TMyClass(MyList.Objects[SomeValidIndex]);  //If it's TObject above, no need for Casting
  5. End;
  6.  

3rd post :
The corresponding object of
Code: Pascal  [Select][+][-]
  1. mylist.Strings[i]
is
Code: Pascal  [Select][+][-]
  1. mylist.Objects[i]

It returns a TObject, so if you want to access the class you initially put there you have to use a typecast as Zvoni already mentioned. And as you wrote that only some lines are associated with an object, you'd better test like this:
Code: Pascal  [Select][+][-]
  1. if Assigned(myList.Objects[i]) then  // equivalent to: if (myList.Objects[i] <> nil) then
  2.   myclass := TMyClass(myList.Objects[i]);
or, even better:
Code: Pascal  [Select][+][-]
  1. if (myList.Objects[i] is TMyClass) then
  2.   myclass := TMyClass(myList.Objects[i]);
The 'is'-test tests for nil as well. And you know for sure that it is actually of the class you want and expect.
Title: Re: TStringList with objects
Post by: Zvoni on December 01, 2022, 11:46:49 am
Works
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. Uses SysUtils, Classes;
  3.  
  4. Var
  5.   MyClass:TObject;
  6.   MyList:TStringList;
  7.   s:AnsiString;
  8.  
  9. begin
  10.   MyList:=TStringList.Create;
  11.   MyList.Add('Test');
  12.   MyList.Objects[0]:=TObject(Integer(5));
  13.   MyClass:=MyList.Objects[0];
  14.   s:=IntToStr(Integer(MyClass)); //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  15.   Writeln(s);
  16.   MyClass:=Nil;
  17.   MyList.Objects[0]:=Nil;
  18.   MyList.Free;
  19. end.
As far as i understood it: Your initial "Object" is an integer.
To get it back you have to cast it back to its "original" type first (in this case Integer), then convert/cast it to String
Title: Re: TStringList with objects
Post by: jipété on December 01, 2022, 12:05:56 pm

Works

Code: Pascal  [Select][+][-]
  1.   ...
  2.   s:=IntToStr(Integer(MyClass)); //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  3.   ...

Something is wrong, somewhere...
Code: Pascal  [Select][+][-]
  1.   s:=IntToStr(Integer(MyClass)); // compiler says : Error: Illegal type conversion: "TObject" to "LongInt"
  2.     // with the word Integer underlined with red
  3.  
  4. //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  5.   s:=Integer(MyClass).ToS // shows ONLY "ToSingle"
:(
Title: Re: TStringList with objects
Post by: Zvoni on December 01, 2022, 12:35:23 pm

Works

Code: Pascal  [Select][+][-]
  1.   ...
  2.   s:=IntToStr(Integer(MyClass)); //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  3.   ...

Something is wrong, somewhere...
Code: Pascal  [Select][+][-]
  1.   s:=IntToStr(Integer(MyClass)); // compiler says : Error: Illegal type conversion: "TObject" to "LongInt"
  2.     // with the word Integer underlined with red
  3.  
  4. //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  5.   s:=Integer(MyClass).ToS // shows ONLY "ToSingle"
:(
No Idea what you're doing wrong. I tested my code above and it works.
To get the Typehelpers you have to include SysUtils in your Uses-Clause
Title: Re: TStringList with objects
Post by: jipété on December 01, 2022, 01:07:10 pm
No Idea what you're doing wrong. I tested my code above and it works.
Maybe it's a WidgetSet problem...
I'm running Lazarus on a Linux Debian 11.5 machine with gtk2 WidgetSet.
And you ?

To get the Typehelpers you have to include SysUtils in your Uses-Clause
Always present :
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   LCLType, LCLIntf,
  9.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Types;
:)
Title: Re: TStringList with objects
Post by: ASerge on December 01, 2022, 03:10:16 pm
Something is wrong, somewhere...
Code: Pascal  [Select][+][-]
  1.   s:=IntToStr(Integer(MyClass)); // compiler says : Error: Illegal type conversion: "TObject" to "LongInt"
  2.     // with the word Integer underlined with red
  3.  
  4. //Or use TypeHelper "ToString" --> s:=Integer(MyClass).ToString;
  5.   s:=Integer(MyClass).ToS // shows ONLY "ToSingle"
:(
The size of the Integer type does not always match the size of a pointer. Use the SizeInt type instead.
Title: Re: TStringList with objects
Post by: jipété on December 01, 2022, 05:30:15 pm
Use the SizeInt type instead.

OMG !
First time in my long programming life that I see that word !  :o

And thanks thanks thanks, it worked perfectly !

May I ask another related question ?
I want to store Word, DWord and QWord (found in help, this one will use SizeUint) : what types using ?
Help only knows SizeInt, SizeUInt and SizeIntArray.

EDIT
Found solutions :
For Word, there is a TWordHelper,
For DWord, coz' it looks like Cardinal, there is a TCardinalHelper.
TinyPortal © 2005-2018