Lazarus

Programming => General => Topic started by: rwebb616 on April 21, 2021, 04:30:43 am

Title: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 04:30:43 am
I'm not sure what I'm missing here...

earlier in my program I did something like this:
Code: Pascal  [Select][+][-]
  1. PlayedWord := TWord.Create(gameplay.category,gameplay.wordstoplay[gameplay.wordindex],false);
  2.   gameplay.wordsplayed.Add(PlayedWord);
  3.  

Tword is a class with Category, Word, and Played - two strings and a boolean respectively.
gameplay.category is a string containing the current category that is being played in the game.
wordstoplay is a TStringList with all the unplayed words from gameplay.category
wordindex is the current word we're on
false is referring to a boolean in Tword that determines if this word was correct or not.
wordsplayed is a TFPObjectList holding all the TWords that have been played

So I'm creating a TWord which takes a category, word and true/false value in the constructor and then just adding it to the FPObjectList wordsplayed.

Later on I'm trying to take one of the Twords in wordsplayed and put it into a TWord variable like this and it's crashing on me:

Code: Pascal  [Select][+][-]
  1. Var
  2.   wordlabel, scorelabel: TLabel;
  3.   wordrec: TWord;
  4.   i, wc: Integer; // counters
  5.   c: Integer; // Count of objectlist
  6.   wrd:string;
  7.   catg:string;
  8.   plyd:boolean;    
  9.  
  10.   For i := 0 To c - 1 Do
  11.   Begin
  12.     wordrec := Tword(PlayedWords[i]);
  13.     wrd:=wordrec.Word;
  14.     catg:=wordrec.Category;
  15.     plyd:=wordrec.Played;
  16.  

It crashes when I try to assign TWord(PlayedWords) to wordrec... I was thinking it was because wordrec is referring to a class so I would have to create it first so I tried putting:
Code: Pascal  [Select][+][-]
  1. wordrec:=Tword.create('cat','word',false)

Above it then it let me assign it but then it crashed when I tried to assign wordrec.word to wrd. 

What am I missing?
Rich
Title: Re: Basic question about classes and objects
Post by: Martin_fr on April 21, 2021, 04:58:55 am
Well, what is missing is the part of your code that crashes....
There are many possibilities.

I am going to pick one of them at random, let make some assumptions.

So you do
Code: Pascal  [Select][+][-]
  1. PlayedWord := TWord.Create(...);
  2. PlayedWords.Add(PlayedWord);
Do you by any chance at any time later do
Code: Pascal  [Select][+][-]
  1. PlayedWord.Destroy; // or .Free or FreeAndNir()
?

If yes, then the TWord in the list is destroyed too.

Look at:
Code: Pascal  [Select][+][-]
  1. PlayedWord1 := TWord.Create(...);
  2. PlayedWord2 := PlayedWord1

You still only have ONE TWord.
You do have 2 references to it. But only one instance. (you only called .Create once).

PlayedWord1 is internally a pointer.
PlayedWord2 := PlayedWord1 only copies that pointer. (pointing to the same memory, allocated once by calling .Create)
PlayedWord1.Destroy; will free the memory. But both variables point to the same memory, so both variables become invalid.

If that is not your problem, then you probably need to share more of your code.

The above problem would normally crash at the line "wrd:=wordrec.Word;" (or later).
But if you enabled certain checks, it may crash at the line you indicated.

--
The other question is, what is in "c" as in "for i := 0 to c - 1"?
Title: Re: Basic question about classes and objects
Post by: egsuh on April 21, 2021, 05:06:50 am
That's because you are adding to wordsplayed

    wordsplayed.Add(PlayedWord);

And retrieving from playedwords

    wordrec := Tword(PlayedWords[ i ]);

If these are actual codes you are using.
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 05:21:44 am
Look at:
Code: Pascal  [Select][+][-]
  1. PlayedWord1 := TWord.Create(...);
  2. PlayedWord2 := PlayedWord1

You still only have ONE TWord.
You do have 2 references to it. But only one instance. (you only called .Create once).

PlayedWord1 is internally a pointer.
PlayedWord2 := PlayedWord1 only copies that pointer. (pointing to the same memory, allocated once by calling .Create)
PlayedWord1.Destroy; will free the memory. But both variables point to the same memory, so both variables become invalid.

If that is not your problem, then you probably need to share more of your code.

The above problem would normally crash at the line "wrd:=wordrec.Word;" (or later).
But if you enabled certain checks, it may crash at the line you indicated.

--
The other question is, what is in "c" as in "for i := 0 to c - 1"?

This helps a lot.  Yes it was crashing where you indicated. 

c is playedwords.count. 

So if I do:
Code: Pascal  [Select][+][-]
  1. word := Tword.create('Cat','Word',false);
  2. playedwords.add(word);

And I don't free "word" .. if word is a local variable what happens when it goes out of scope?

Then the next question is how do I later assign a variable to Tword(playedwords[0])?  Or is my way working but likely I had done word.free? I'll have to go back and look.
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 05:28:14 am
That's because you are adding to wordsplayed

    wordsplayed.Add(PlayedWord);

And retrieving from playedwords

    wordrec := Tword(PlayedWords[ i ]);

If these are actual codes you are using.

That's because playedwords is the argument to the procedure .. so I'm passing wordsplayed into playedwords.

Here is the full code but there is a lot of extraneous information and likely a lot of defined stuff that's not being used.  Haven't gone through and done cleanup so take that into consideration:
Code: Pascal  [Select][+][-]
  1. procedure TfGameboard.PlayerSummary(PlayedWords: TFPObjectList);
  2. Var
  3.   wordlabel, scorelabel: TLabel;
  4.   wordrec: TWord;
  5.   i, wc: Integer; // counters
  6.   c: Integer; // Count of objectlist
  7.   s: Integer; // Score (only correct words from the above word count - played = true)
  8.   p: Integer; // Pages of words
  9.   r: Integer; // Remainder of words after full pages
  10.   d: Integer; // Duration of delay between pages in ms
  11.   wrd:string;
  12.   catg:string;
  13.   plyd:boolean;
  14.  
  15. Begin
  16.   // This proc will change the screen to the score screen, hide all the labels and then place
  17.   // all played words onto a TPanel using top to bottom left to right display characteristics
  18.   // If there are more than 9 words then it will flip screens for a set amount of seconds
  19.  
  20.   // Init Variables
  21.   i := 0;
  22.   wc := 0;
  23.   c := playedwords.Count;
  24.   s := 0;
  25.   p := c Div 9;
  26.   r := c Mod 9;
  27.   If (r > 0) Then Inc(p);
  28.   d := 3000;
  29.  
  30.   // Set background to Summary screen and hide all labels
  31.   setimage('SCORES');
  32.   HideLabels(0);
  33.  
  34.   // Get count of only "correct" words:
  35.   For i := 0 To c - 1 Do
  36.   Begin
  37.     WordRec := TWord(playedwords[i]);
  38.     If WordRec.Played Then
  39.       Inc(s);
  40.   End;
  41.  
  42.   // Add panel control and position and set properties
  43.   WordPanel := TPanel.Create(self);
  44.   With WordPanel Do
  45.   Begin
  46.     left := 48;
  47.     Height := 640;
  48.     Top := 344;
  49.     Width := 1824;
  50.     BevelOuter := bvNone;
  51.     ChildSizing.ControlsPerLine := 3;
  52.     ChildSizing.LeftRightSpacing := 50;
  53.     ChildSizing.HorizontalSpacing := 110;
  54.     ChildSizing.VerticalSpacing := 40;
  55.     ChildSizing.Layout := cclTopToBottomThenLeftToRight;
  56.     ClientHeight := 640;
  57.     ClientWidth := 1824;
  58.     ParentBackground := False;
  59.     ParentColor := False;
  60.     Parent := Self;
  61.     Visible := True;
  62.     ParentBackground := True;
  63.   End;
  64.  
  65.   // Set and Position Score label
  66.   scorelabel := setlabel('Score', s.tostring, 130, 6);
  67.   With scorelabel Do
  68.   Begin
  69.     autosize := False;
  70.     left := 728;
  71.     Height := 240;
  72.     top := 30;
  73.     Width := 472;
  74.     alignment := taCenter;
  75.     constraints.MaxWidth := 500;
  76.     constraints.MinHeight := 175;
  77.     Layout := tlCenter;
  78.     parent := self;
  79.     font.Name := 'Roboto Mono Bold';
  80.     Visible := True;
  81.   End;
  82.  
  83.   // get word labels for all the words and set their properties and place into the labels structure
  84.   For i := 0 To c - 1 Do
  85.   Begin
  86.     wordrec := Tword.Create('Cat','word',false);
  87.     wordrec := Tword(PlayedWords[i]);
  88.     wrd:=wordrec.Word;
  89.     catg:=wordrec.Category;
  90.     plyd:=wordrec.Played;
  91.     wordlabel := SetLabel('Word' + i.ToString, wordrec.Word, 80, 6);
  92.     With WordLabel Do
  93.     Begin
  94.       AutoSize := False;
  95.       Alignment := tacenter;
  96.       constraints.MaxWidth := 797;
  97.       constraints.MinWidth := 797;
  98.       Width := 797;
  99.       Layout := tlCenter;
  100.       OptimalFill := True;
  101.       Parent := WordPanel;
  102.       Tag := 1;
  103.       If Not wordrec.played Then
  104.         font.Color := Tcolor($e49435);
  105.       font.Name := 'Roboto Medium';
  106.     End;
  107.   End;
  108.  
  109.   // Display the words on the Tpanel control and flip pages if necessary (hide/show labels to make look like flipping)
  110.   While Not gameloop.continue Do
  111.   Begin
  112.     hidelabels(1);
  113.     i := 0;
  114.     wc := 0; // Word Count to count up to max words
  115.     While wc <= c - 1 Do
  116.     Begin
  117.       If i <= 5 Then  // Max amount of words on panel
  118.       Begin
  119.         wordlabel := getlabel('Word' + wc.tostring);
  120.         wordlabel.Visible := True;
  121.         Inc(i);
  122.         Inc(wc);
  123.       End
  124.       Else
  125.       Begin
  126.         If gameloop.continue Then break;  // Break out if user clicked "continue"
  127.         delay(d); // delay before going to next "page"
  128.         hidelabels(1);
  129.         i := 0; //reset page count but not overall word count
  130.       End;
  131.     End;
  132.     If gameloop.continue Then break;
  133.     delay(d);
  134.   End;
  135.   WordPanel.Free;
  136. End;                                                                    
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 05:30:39 am
Do you by any chance at any time later do
Code: Pascal  [Select][+][-]
  1. PlayedWord.Destroy; // or .Free or FreeAndNir()
?

I went back and checked, and yes I did - that is now commented out and left there as a reminder in big bold letters  :-[
Title: Re: Basic question about classes and objects
Post by: Martin_fr on April 21, 2021, 06:00:57 am
So if I do:
Code: Pascal  [Select][+][-]
  1. word := Tword.create('Cat','Word',false);
  2. playedwords.add(word);

And I don't free "word" .. if word is a local variable what happens when it goes out of scope?
If you did not make any copy of the reference (that is, if you did not add it to any list, or assigned it to a global var), then you have a memory leak. There is no garbage collection for classes.

Code: Pascal  [Select][+][-]
  1. word := TWord.Create
Includes
Code: Pascal  [Select][+][-]
  1. word := getmem(TWord.InstanceSize;
And "Destroy" is the call to free that memory.

And any
Code: Pascal  [Select][+][-]
  1. Word1 := Word2;
copies a pointer to that memory.

And if you do Word2.Destroy then Word1 points to invalid memory.
(sometimes the invalid memory still has ghost data, and appears to work, but that is never stable)

In other words, you need to keep track which variables, and which lists still hold references to the instance (the allocated memory).


Quote
Then the next question is how do I later assign a variable to Tword(playedwords[0])?  Or is my way working but likely I had done word.free? I'll have to go back and look.

The typecast is fine as it was. You put a TWord into the list, you cast it back when you take it out.

Or you do:
Code: Pascal  [Select][+][-]
  1. uses fgl;
  2. type
  3.   TWordList = specialize TFPGList<TWord>;
  4. var PlayedWords: TWordList;
  5.  
  6. begin
  7.   wordrec := PlayedWords[i]; // already returns the correct type
  8.  


You might want to rethink your naming...

"word_rec" sounds like it is of type
Code: Pascal  [Select][+][-]
  1. type TSomething = record ... end;
which differs from a class.

PlayedWord vs PlayedWords => you gonna misread it at sometime....
use
PlayedWord and PlayedWordsList

Even all the integers c,s,r,d,p....
Give them longer names.
i,c are fine.
Use syncro edit https://wiki.lazarus.freepascal.org/New_IDE_features_since#Syncron-Edit , once you written the code. (That is what I do, start with short names, then rename them)
Select the entire procedure and rename them.
(Imho 3 to 4 single letter vars are ok, but over that and give them longer names)


Just my 2 cents on the naming.
Title: Re: Basic question about classes and objects
Post by: egsuh on April 21, 2021, 06:05:14 am
Quote
playedwords is the argument to the procedure

Actually I didn't think that would be the reason.  Just wanted to check such basic points.

I recommend you to use generics if you are to use TObjectList, etc. for classes, so that you do not have to typecast the item every time.

     WordList = specialize TFPGObjectList<TWord>;

At the same time you may think of setting TFPGObjectList.FreeObjects to True, becuase removing an item from the list will free the object. 

Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 08:24:46 am
If you did not make any copy of the reference (that is, if you did not add it to any list, or assigned it to a global var), then you have a memory leak. There is no garbage collection for classes.

I guess I'm confused on how to properly keep track of this stuff.  Should I be defining a global variable as a Tword and then just re-use it when I need to reference one of the TWords in my list?  I can see it helps to understand the memory implications.

Quote
You might want to rethink your naming...

"word_rec" sounds like it is of type
Code: Pascal  [Select][+][-]
  1. type TSomething = record ... end;
which differs from a class.

Yes, this is because Tword started out it's life as a record and then I re-wrote it as a class because I wanted to delve into learning how they work.

Quote
PlayedWord vs PlayedWords => you gonna misread it at sometime....
use
PlayedWord and PlayedWordsList

Agreed - I was already thinking about renaming that one.

Quote
Even all the integers c,s,r,d,p....
Give them longer names.
i,c are fine.

Usually I do - this is the only procedure where I used a bunch of them because I was trying to write it quick.  I also had developed that procedure separately from this application.

Quote
Use syncro edit https://wiki.lazarus.freepascal.org/New_IDE_features_since#Syncron-Edit , once you written the code. (That is what I do, start with short names, then rename them)
Select the entire procedure and rename them.
(Imho 3 to 4 single letter vars are ok, but over that and give them longer names)

That is cool - I didn't know about that - I was going to research multi-line editing too similar to VSCode where you can ctrl-click multiple places and have multiple cursors and edit all of them at once.
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 08:26:15 am
Quote
playedwords is the argument to the procedure

Actually I didn't think that would be the reason.  Just wanted to check such basic points.

I recommend you to use generics if you are to use TObjectList, etc. for classes, so that you do not have to typecast the item every time.

     WordList = specialize TFPGObjectList<TWord>;

At the same time you may think of setting TFPGObjectList.FreeObjects to True, becuase removing an item from the list will free the object.

Yes the typecasting gets to be a bit much... I'll have to read up on generics - never used the feature yet.  It looks pretty straight-forward.. so if you specialize and Object list then that is the type that objectlist will automatically expect and contain?
Title: Re: Basic question about classes and objects
Post by: egsuh on April 21, 2021, 09:30:34 am
Quote
if you specialize and Object list then that is the type that objectlist will automatically expect and contain?

Yes. Compiler knows the class type, so you don't have to typecast.   

https://wiki.freepascal.org/Generics (https://wiki.freepascal.org/Generics)
Title: Re: Basic question about classes and objects
Post by: kupferstecher on April 21, 2021, 12:58:55 pm
I guess I'm confused on how to properly keep track of this stuff.  Should I be defining a global variable as a Tword and then just re-use it when I need to reference one of the TWords in my list?  I can see it helps to understand the memory implications.

Take your line of code:
Code: Pascal  [Select][+][-]
  1.   wordrec:=Tword.create('cat','word',false);
With "Tword.create" you call the Constructor Create. When this is done, you implicitly advice to reserve memory on the heap (dynamic memory) for the object*. After it is reserved, the actual procedure Create is called, where you can do the initialization stuff of the object. This procedure is actually a function that returns the objects address. With "wordrec:=" you assign this address to the variable wordrec. The actual instance lies somewhere on the heap and the variable wordrec is only a reference (this is Freepascal/Delphi-specific). The reference works like a hidden pointer, when you pass a variable of class type, you always pass the address. You can freely pass around the object by just assigning it to an other variable. The memory of the object itself is never touched by that. Even if your variable "wordrec" goes out of scope and no other variable carries the address of the object, it will still be in the memory. So although its still there on the heap, but you don't know where, so you wouldn't have any chance to access it. Thats whats called a 'memory leak'. (Btw. the operating system will clean that up when you close your program, but until that its lost memory). So it should be clear, it doesn't matter if the reference is a local or a global variable, if it is part of a list, a value of an other class or whatever, it always is just a reference to the actual object, that lies on the heap. And the compiler doesn't insert any code for copying or creating or deleting an object for passing the object around in variables.

Even the following would be a legit line of code as well:
Code: Pascal  [Select][+][-]
  1.  Tword.create('cat','word',false);
So the 'returned' address is discarded. And in circumstances this can be useful, e.g. if you add the object to a list within your constructor.

That could look as follows:

Code: Pascal  [Select][+][-]
  1. Constructor TWord.Create(...);
  2. begin
  3.   gameplay.wordsplayed.Add(self);
  4.   [...]
  5. end;
 
"Self" is the instance you created when you called "create". Well actually its also just a reference to the instance. And you can always use it in methods (i.e. functions/procedures in classes), not only in the constructor.

*Object means here the instance of a class, and NOT the type "object" which works similar as "class".
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 02:39:22 pm
You said:
Quote
Even if your variable "wordrec" goes out of scope and no other variable carries the address of the object, it will still be in the memory. So although its still there on the heap, but you don't know where, so you wouldn't have any chance to access it. Thats whats called a 'memory leak'.

So if I define wordrec in global scope (interface section) and then I assign it when creating a TWord (wordrec := Tword.create) and then add wordrec to a list (playedwordslist.add(wordrec)) for the sake of example lets call that index 0 .. and then later do the same thing (wordrec := Tword.create) and then add to playedwordslist (index 1) can I go back and forth between index 0 and 1 using (wordrec := Tword(playedwordslist[0]) and [1])  without having a memory leak as long as wordrec is global in scope?

Title: Re: Basic question about classes and objects
Post by: Zvoni on April 21, 2021, 02:50:38 pm
You mean something like
Code: Pascal  [Select][+][-]
  1. ....
  2. Var MyWord1, MyWord2:TWord;
  3. ....
  4. MyWord1:=TWord.Create('cats','word',false);
  5. //DoSomething with MyWord1
  6. MyWord2:=MyWord1;
  7. //DoSomething with MyWord2
  8. MyWord1:=TWord.Create('dogs','word',false);
  9. //DoSomething with MyWord1
  10.  
  11. //Never once calling Free on any of the Variables
  12.  
Title: Re: Basic question about classes and objects
Post by: Martin_fr on April 21, 2021, 03:01:06 pm
You said:
Quote
Even if your variable "wordrec" goes out of scope and no other variable carries the address of the object, it will still be in the memory. So although its still there on the heap, but you don't know where, so you wouldn't have any chance to access it. Thats whats called a 'memory leak'.

So if I define wordrec in global scope (interface section) and then I assign it when creating a TWord (wordrec := Tword.create) and then add wordrec to a list (playedwordslist.add(wordrec)) for the sake of example lets call that index 0 .. and then later do the same thing (wordrec := Tword.create) and then add to playedwordslist (index 1) can I go back and forth between index 0 and 1 using (wordrec := Tword(playedwordslist[0]) and [1])  without having a memory leak as long as wordrec is global in scope?

yes you can.
But you do not need a global var.

Code: Pascal  [Select][+][-]
  1. procedure NewWord;
  2. var Word: TWord;
  3. begin
  4.   Word := TWord.create();
  5.   WordList.Add(Word);
  6. end;

When the procedure is exited, then "Word" goes out of scope. But that just means the pointer stored in word.

The actual object (instance) lives on. (You did not call destroy, after all).
And you still have a pointer in the list. So you can still access the object.


But when you do
Code: Pascal  [Select][+][-]
  1. procedure NewWord;
  2. var Word: TWord;
  3. begin
  4.   Word := TWord.create();
  5.   WordList.Add(Word);
  6.   Word.Destroy
  7. end;
You still have a pointer in the list. But it points to invalid memory (and there is no way to test for that in your code / assigned will be true / <> nil will be true / yet it is not valid)

The pointer in the variable, and the instance in memory are 2 separate things.

- The pointer is controlled by := or list.add.
- The instance memory is controlled by Create/Destroy

Using :=/list.add does not change the instance memory.
And using Create/Destroy does not affect any pointers.

Well "Word := TWord.Create" does both. But it has  := and create in it.

"Word := nil" does not change the memory (but you need another copy of the pointer to get to it, because this pointer is no longer... / but any pointer in a list would still be)






You can do the above with a global var "Word". But there is no advantage.
On the long time, the global var can get in the way. Because you have more work to keep track where you use it. I.e. if you use it in a nested function, you might overwrite the outer value:
Code: Pascal  [Select][+][-]
  1. var Word: TWord; //global
  2. procedure NewWord;
  3. begin
  4.   Word := TWord.create();
  5.   WordList.Add(Word);
  6. end;
  7.  
  8. procedure Foo;
  9. begin
  10.   Word := wordlist [n];
  11.   if condition then NewWord;
  12.   word.foo;  // this accesses the word replaced in new Word, IF condition was true. And you might expect the "wordlist[n]"
  13. end;
  14.  

Whereas if both have a local var, then it is much more readable.

Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 03:21:59 pm
You mean something like
Code: Pascal  [Select][+][-]
  1. ....
  2. Var MyWord1, MyWord2:TWord;
  3. ....
  4. MyWord1:=TWord.Create('cats','word',false);
  5. //DoSomething with MyWord1
  6. MyWord2:=MyWord1;
  7. //DoSomething with MyWord2
  8. MyWord1:=TWord.Create('dogs','word',false);
  9. //DoSomething with MyWord1
  10.  
  11. //Never once calling Free on any of the Variables
  12.  

Yes, I guess this is the same thing.. Looks like Martin answered it too..
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 03:24:41 pm

The pointer in the variable, and the instance in memory are 2 separate things.

- The pointer is controlled by := or list.add.
- The instance memory is controlled by Create/Destroy

Using :=/list.add does not change the instance memory.
And using Create/Destroy does not affect any pointers.


Ok then it sounds like I was doing everything right other than freeing the local variable (which I now understand was freeing the instance instead). 
Title: Re: Basic question about classes and objects
Post by: Zvoni on April 21, 2021, 03:32:31 pm
Yes, Martin's answer is pretty comprehensive.

the concept of variables pointing to a memory-address, and what happens if you "loose" this reference....
I often have discussions with people not understanding that concept, and i try to explain it the following way:
"You have a bank-account, and that's where your money (physically) is. The Bank is your Memory/RAM, the safe with number 123456789 is the actual address of your safe within the bank."
"NOW, you have a credit-card associated with that bank-account of yours. And you carry that credit-card with you all around the world. But your credit-card is still pointing to that safe-number within your Bank, where it is stored"
"The credit-card is the variable, your safe 123456789 is your actual instance"
"What happens if you "loose" your credit-card? Correct! You cannot access your money anymore"


Curiously enough, pretty much everyone understands that......
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 21, 2021, 08:09:13 pm
"What happens if you "loose" your credit-card? Correct! You cannot access your money anymore"

Yes, however losing my credit card (or keeping with the analogy, cutting up my credit card (destroy) ) doesn't orphan my bank account.  I can still get another credit card to access the same account.    :o

Rich
Title: Re: Basic question about classes and objects
Post by: lucamar on April 21, 2021, 09:10:23 pm
Yes, however losing my credit card (or keeping with the analogy, cutting up my credit card (destroy) ) doesn't orphan my bank account.  I can still get another credit card to access the same account.    :o

Because you have another "pointer" to that money: the address of the bank or a phone number or whatever, along with your identity card (or similar means).

Perhaps a better example would be one of those fabled anonymous, numbered swish accounts: if you loose the "accreditation", the money is left in "limbo" inside the bank.
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 22, 2021, 01:09:34 am
Here is a good example where I'm not sure how to handle it.  This is a function that returns a Tstringlist for use elsewhere:
Code: Pascal  [Select][+][-]
  1. function TData.GetCategories: TStringList;
  2. var
  3.   i : integer;
  4.   cats : TStringlist;
  5.   word : TWord;
  6. begin
  7.   cats := TStringlist.Create;
  8.   for i:=0 to Words.Count - 1 do begin;
  9.     word := Tword(words[i]);
  10.     if i = 0 then begin
  11.       cats.Add(word.Category);
  12.     end
  13.     else
  14.       if (cats.IndexOf(word.Category) = -1) then begin
  15.         cats.Add(word.Category);
  16.       end;
  17.   end;
  18.   Result := cats;
  19.   //cats := nil;
  20.   //cats.Free;
  21. end;

So I have Tdata instantiated as GameData in my main form and GameData is a global variable... so I can do:

Code: Pascal  [Select][+][-]
  1. var
  2.   myVar : TStringlist;
  3. Begin
  4.   myVar := GameData.GetCategories;
  5.  

As you can see I had commented out the cats:=nil and cats.free but if I call this procedure as shown... cats will be created for the function.  It will return the TStringList but then cats will go out of scope.  The stringlist will now be used by myVar so to clean up after myself would I just do myVar.free?

Sorry for all the questions - it's becoming more clear just need to get my head around this completely.

Rich
Title: Re: Basic question about classes and objects
Post by: lucamar on April 22, 2021, 01:29:02 am
This is easier and precludes the question:

Code: Pascal  [Select][+][-]
  1. function TData.GetCategories: TStringList;
  2. var
  3.   i : integer;
  4.   word : TWord;
  5. begin
  6.   Result := TStringlist.Create;
  7.   for i:=0 to Words.Count - 1 do begin
  8.     word := Tword(words[i]);
  9.     if i = 0 then begin
  10.       Result.Add(word.Category);
  11.     end
  12.   else
  13.       if (Result.IndexOf(word.Category) = -1) then begin
  14.         Result.Add(word.Category);
  15.       end;
  16.   end;
  17. end;

Could be even shorter, by setting the string list to reject duplicates and not using an intermediate var:

Code: Pascal  [Select][+][-]
  1. function TData.GetCategories: TStringList;
  2. var i : integer;
  3. begin
  4.   Result := TStringlist.Create;
  5.   Result.Duplicates := dupIgnore;
  6.   for i:=0 to Words.Count - 1 do
  7.     Result.Add(Tword(words[i]).Category);
  8. end;
Title: Re: Basic question about classes and objects
Post by: Martin_fr on April 22, 2021, 01:30:47 am
Well to go for the banking analogy.

The instance is your bank account. (Or one of them if you have many)
.Create() means you go to the bank and open an account
.Destroy() means you close the account (same as Free)

When you open the account you get a card with it (credit/debit/whatever).
You can have any amount of cards. But of course to pay in a store, you need at least one card.

:= is a magic card copy maker ;)

   word := TWord.Create;
word is you banking card.

   foo := word;
Now you have 2 cards. You can use either. But it always affects the same account.

If you do
  word.Destroy
you close the account.
All the other cards also stop working.
Any attempt to use any of those card, will get you into trouble because payment fails.
You also are not allowed to attempt closing the account again (maybe by using another card to close it). The bank is really strict on that.

While the account is open, and so long as you always hold on to one card, you are fine. You can make new copies.

Also cards to not have to be named. They can be slots in a list.


As I said, once the account is closed, none of the cards work any more.

But they still look like cards. You even can still copy them (though the new copies will not work either).

So best practice is to cut the cards, and mark them as no longer working.
To cut a card
   word  := nil; // does not include closing the account. Cutting a working card only affects the one card.
or
    FreeAndNil(word) // includes closing the account. Also, only cuts this one card

If the account is closed, you have to find each card and cut it. If you don't, and you accidentally try to use it to pay, you go to jail. 
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 22, 2021, 04:28:09 am

Could be even shorter, by setting the string list to reject duplicates and not using an intermediate var:

Code: Pascal  [Select][+][-]
  1. function TData.GetCategories: TStringList;
  2. var i : integer;
  3. begin
  4.   Result := TStringlist.Create;
  5.   Result.Duplicates := dupIgnore;
  6.   for i:=0 to Words.Count - 1 do
  7.     Result.Add(Tword(words[i]).Category);
  8. end;

I tried this and it looks like duplicates are being added... my checklistbox has a category entry for each item in each category so if there are 5 "cars" I see 5 cars entries in the category list. 

dupIgnore not working maybe?  I like the simplicity of this though! Thanks for your input!
Rich
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 22, 2021, 04:30:17 am

If the account is closed, you have to find each card and cut it. If you don't, and you accidentally try to use it to pay, you go to jail.

I think I'm finally starting to get the idea!  Thank you for being patient and explaining things clearly.  My next study session is going to involve the generics that you and others have mentioned. 

Rich
Title: Re: Basic question about classes and objects
Post by: egsuh on April 22, 2021, 04:40:15 am
Code: Pascal  [Select][+][-]
  1.     var
  2.       myVar : TStringlist;
  3.     Begin
  4.       myVar := GameData.GetCategories;
  5.  
  6.       // Do whatever necessary with myVar
  7.       // and you'd better free it when its use is over
  8.       myVar.Free;
  9.    end;
  10.  
         

Other guys gave full explanation. I'd like to add that you'd better have the habit of freeing created object when its use is over. It will take memory, if not freed.

Regarding dupIgnore, please check whether your texts are exactly the same --- case and leading or training blanks.
Title: Re: Basic question about classes and objects
Post by: rwebb616 on April 22, 2021, 04:54:03 am
I figured it out - in order for Tstringlist.duplicates to have any effect the list must be sorted (TStringlist.sorted:=True)

Not sure yet whether I want my list sorted... hmm....
Rich
Title: Re: Basic question about classes and objects
Post by: Thaddy on April 22, 2021, 09:08:29 am
Not sure yet whether I want my list sorted... hmm....
Rich
Although technically possible, if the list is not sorted detecting duplicates can soon be impossibly slow.
Title: Re: Basic question about classes and objects
Post by: dseligo on April 22, 2021, 09:35:44 am
Well to go for the banking analogy.

What puzzles me in this analogy is this: Why do I have to close my account in the end (Free, Destroy) if I still have money on the account?
What will happen with my money (created object) then?  :)

Does bank leak money if accounts aren't closed (as programs leaks memory)?  :)

P.S.: OK, probably the owner of account dies in the end (program closes) and money goes to inheritants (OS).
Title: Re: Basic question about classes and objects
Post by: egsuh on April 22, 2021, 09:44:33 am
Quote
What will happen with my money (created object) then?

This is a swiss bank account, and the bank will charge you for keeping the account open, and if you do not pay, Interpol will inspect you for keeping possible dark money.  :D

If the memory is not freed, it is regarded as taken and not available to other applications.
Title: Re: Basic question about classes and objects
Post by: dseligo on April 22, 2021, 10:22:38 am
Quote
What will happen with my money (created object) then?

This is a swiss bank account, and the bank will charge you for keeping the account open, and if you do not pay, Interpol will inspect you for keeping possible dark money.  :D

If the memory is not freed, it is regarded as taken and not available to other applications.

Or: if account isn't closed and all cards are lost, then safe is occupied, and no one can access it anymore (not even bank).
Title: Re: Basic question about classes and objects
Post by: Martin_fr on April 22, 2021, 03:48:38 pm
Well to go for the banking analogy.

What puzzles me in this analogy is this: Why do I have to close my account in the end (Free, Destroy) if I still have money on the account?
What will happen with my money (created object) then?  :)

The money is less important in that case, but yes its gone for good. (i.e. inaccessible)

But the bank only has a certain amount of accounts (They need physical space to store each account). So evtl, the bank is full, and you can no longer open new accounts.
Besides that, it's good practice to be tidy, help the bank to be tidy too, and get rid of unused accounts.
Title: Re: Basic question about classes and objects
Post by: Zvoni on April 22, 2021, 03:54:17 pm
And now everybody knows, how the IRS sniffs out the "black-money-accounts":
They are memory-leaks  :D :D :D :D :D
Title: Re: Basic question about classes and objects
Post by: dseligo on April 22, 2021, 07:15:22 pm
And now everybody knows, how the IRS sniffs out the "black-money-accounts":
They are memory-leaks  :D :D :D :D :D

They use Heaptrc unit (-gh) :D
TinyPortal © 2005-2018