Recent

Author Topic: Basic question about classes and objects  (Read 4841 times)

rwebb616

  • Full Member
  • ***
  • Posts: 133
Basic question about classes and objects
« 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

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9794
  • Debugger - SynEdit - and more
    • wiki
Re: Basic question about classes and objects
« Reply #1 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"?

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: Basic question about classes and objects
« Reply #2 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.

rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #3 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.

rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #4 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;                                                                    

rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #5 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  :-[

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9794
  • Debugger - SynEdit - and more
    • wiki
Re: Basic question about classes and objects
« Reply #6 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.

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: Basic question about classes and objects
« Reply #7 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. 


rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #8 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.

rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #9 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?

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: Basic question about classes and objects
« Reply #10 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

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Basic question about classes and objects
« Reply #11 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".

rwebb616

  • Full Member
  • ***
  • Posts: 133
Re: Basic question about classes and objects
« Reply #12 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?


Zvoni

  • Hero Member
  • *****
  • Posts: 2319
Re: Basic question about classes and objects
« Reply #13 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.  
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

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9794
  • Debugger - SynEdit - and more
    • wiki
Re: Basic question about classes and objects
« Reply #14 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.


 

TinyPortal © 2005-2018