Recent

Author Topic: TStringList with objects  (Read 10101 times)

PascalDragon

  • Hero Member
  • *****
  • Posts: 4880
  • Compiler Developer
Re: TStringList with objects
« Reply #60 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:
Quote
It is bad programming practice to call Destroy directly. It is better to call the Free 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 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.

MarkMLl

  • Hero Member
  • *****
  • Posts: 5864
Re: TStringList with objects
« Reply #61 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
« Last Edit: February 26, 2022, 07:38:43 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jcmontherock

  • Full Member
  • ***
  • Posts: 160
Re: TStringList with objects
« Reply #62 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.

PascalDragon

  • Hero Member
  • *****
  • Posts: 4880
  • Compiler Developer
Re: TStringList with objects
« Reply #63 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.

MarkMLl

  • Hero Member
  • *****
  • Posts: 5864
Re: TStringList with objects
« Reply #64 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
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jipété

  • Jr. Member
  • **
  • Posts: 55
Re: TStringList with objects
« Reply #65 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.

Zvoni

  • Hero Member
  • *****
  • Posts: 1604
Re: TStringList with objects
« Reply #66 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
« Last Edit: December 01, 2022, 11:50:25 am by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

jipété

  • Jr. Member
  • **
  • Posts: 55
Re: TStringList with objects
« Reply #67 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"
:(

Zvoni

  • Hero Member
  • *****
  • Posts: 1604
Re: TStringList with objects
« Reply #68 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
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

jipété

  • Jr. Member
  • **
  • Posts: 55
Re: TStringList with objects
« Reply #69 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;
:)

ASerge

  • Hero Member
  • *****
  • Posts: 2049
Re: TStringList with objects
« Reply #70 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.

jipété

  • Jr. Member
  • **
  • Posts: 55
Re: TStringList with objects
« Reply #71 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.
« Last Edit: December 01, 2022, 06:46:14 pm by jipété »

 

TinyPortal © 2005-2018