Recent

Author Topic: Class and normal (per-instance) fields.  (Read 1935 times)

MarkMLl

  • Hero Member
  • *****
  • Posts: 8038
Class and normal (per-instance) fields.
« on: June 24, 2024, 11:42:15 am »
I've been scratching my head for the last few hours since a field was being overwritten when I didn't expect it. I think I've worked out what was going on, but still feel it worth raising for discussion. FPC 3.2.2, other versions untested.

Code: Pascal  [Select][+][-]
  1. program testSL;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. uses
  6.   Classes;
  7.  
  8. type
  9.   String31= string[31];
  10.   TVertexDegs= record
  11.                  lat, lon: double
  12.                end;    
  13.  
  14.   (* This is primarily a container for binary values extracted from an ADS-B block,
  15.     but might also be used in a couple more roles relating to caching etc. In most
  16.     cases it is attached to the Objects[0] of the TFlight (in effect a TStringList)
  17.     containing the original (i.e. text-format) data.
  18.   *)
  19.   TTicket= class
  20.     class var InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  21.     class var RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  22.     Timestamp: TDateTime;
  23.     IcaoAddress: string31;
  24.     constructor Create(now: TDateTime);
  25.  
  26.     (* Return a newly-created TTicket filled in from this one.
  27.     *)
  28.     function Duplicate(): TTicket;            
  29.   end;
  30.  
  31.  
  32. constructor TTicket.Create(now: TDateTime);
  33.  
  34. begin
  35.   inherited Create;
  36.   Timestamp := now;
  37.   self.IcaoAddress := '<none>'
  38. end { TTicket.Create } ;
  39.  
  40.  
  41. (* Return a newly-created TTicket filled in from this one.
  42. *)
  43. function TTicket.Duplicate(): TTicket;
  44.  
  45. var
  46.   original: TTicket;
  47.   scratch: string31;
  48.  
  49. begin
  50.   original := self;
  51.   scratch := original.IcaoAddress;
  52.   scratch := original.IcaoAddress;
  53.  
  54. // At this point, the field is still as expected so has not been overwritten by
  55. // the action of reading it.
  56.  
  57.   result := TTicket.Create(0.0 { self.Timestamp});
  58.   scratch := original.IcaoAddress;
  59.  
  60. // The field has now been overwritten to the value in Create().
  61.  
  62. end { TTicket.Duplicate } ;
  63.  
  64.  
  65. var
  66.   t1, t2: TTicket;
  67.  
  68. begin
  69.   t1 := TTicket.Create(0.0 { Now() } );
  70.   t1.IcaoAddress := 'Initialised';
  71. WriteLn(t1.IcaoAddress);
  72.   t2 := t1.Duplicate;
  73. WriteLn(t1.IcaoAddress)
  74. end.
  75.  

With the code as above, my output is

Code: Text  [Select][+][-]
  1. $ ./testSL
  2.  
  3. Initialised
  4. <none>
  5.  

i.e. the IcaoAddress field is being overwritten when I go to create a new instance.

What's actually happening is that the  class  modifier is running forward. If I insert an explicit  public  like this

Code: Pascal  [Select][+][-]
  1.   TTicket= class
  2.     class var InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  3.     class var RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  4. public
  5.     Timestamp: TDateTime;
  6.     IcaoAddress: string31;
  7. ...
  8.  

then behaviour is as expected.

As far as I can see, the manual doesn't describe  class  as a visibility modifier, so while I think I now see /what/ is happening I'm a little nonplussed as to /why/.

Comments would be appreciated.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

alpine

  • Hero Member
  • *****
  • Posts: 1303
Re: Class and normal (per-instance) fields.
« Reply #1 on: June 24, 2024, 04:56:07 pm »
As far as I can see, the manual doesn't describe  class  as a visibility modifier, so while I think I now see /what/ is happening I'm a little nonplussed as to /why/.
https://www.freepascal.org/docs-html/ref/refsu24.html#x74-980006.3.2 contains a term 'class var block' and that should define it as valid for the subsequent fields until replaced by e.g. public, private, etc.
As opposed to the static modifier which is for the last field/method only. I can agree it is a bit misleading.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

MarkMLl

  • Hero Member
  • *****
  • Posts: 8038
Re: Class and normal (per-instance) fields.
« Reply #2 on: June 24, 2024, 05:07:36 pm »
As far as I can see, the manual doesn't describe  class  as a visibility modifier, so while I think I now see /what/ is happening I'm a little nonplussed as to /why/.
https://www.freepascal.org/docs-html/ref/refsu24.html#x74-980006.3.2 contains a term 'class var block' and that should define it as valid for the subsequent fields until replaced by e.g. public, private, etc.
As opposed to the static modifier which is for the last field/method only. I can agree it is a bit misleading.

Thanks for that. Use of /block/ though would imply that it's got its own /end/... it looks as though it's best considered like a visibility modifier.

Which raises the question of how it works with other modifiers. it looks as though what I wrote was actually

Code: Pascal  [Select][+][-]
  1. TTicket= class
  2. public class
  3.   var InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  4.   var RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  5. public
  6. ...
  7.   end;
  8.  

hence I find myself wondering whether "private class" or even "strict private class" would work as expected.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jamie

  • Hero Member
  • *****
  • Posts: 6735
Re: Class and normal (per-instance) fields.
« Reply #3 on: June 24, 2024, 11:25:57 pm »
after close look at it.  :D

 It seems the compiler is ok, it's just that VAR being presented in a block is the same as it would be in a start of a block statement.

 Start a of "VAR" makes all following members also a VAR and in this case, because they are classes which are the same as STATIC and your string31 was in it, which made it a static and got changed.

 I find that if you use the keyword "STATIC" on each item instead of CLASS VAR, then they process correctly and you don't need a visible modifier to finalize a Class VAR section.

 This was a good exersize btw, I like it !  :)
The only true wisdom is knowing you know nothing

MarkMLl

  • Hero Member
  • *****
  • Posts: 8038
Re: Class and normal (per-instance) fields.
« Reply #4 on: June 25, 2024, 08:14:07 am »
It seems the compiler is ok, it's just that VAR being presented in a block is the same as it would be in a start of a block statement.

That's a good point. Is it actually needed, or would simply making it look like an ordinary field work?

Code: Pascal  [Select][+][-]
  1. TTicket= class
  2.     class InfieldDegs: TVertexDegs; (* Used for range calculation           *) // This is per-class
  3.     class RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  4.     Timestamp: TDateTime; // This is per-instance
  5.     IcaoAddress: string31;
  6.  

Quote
This was a good exersize btw, I like it !  :)

You're welcome. Cost me quite a lot of grief since it was in a fairly complex bit of caching code which turned out not to work after plugging it into the bigger program.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

PascalDragon

  • Hero Member
  • *****
  • Posts: 5759
  • Compiler Developer
Re: Class and normal (per-instance) fields.
« Reply #5 on: June 25, 2024, 09:19:46 pm »
It seems the compiler is ok, it's just that VAR being presented in a block is the same as it would be in a start of a block statement.

That's a good point. Is it actually needed, or would simply making it look like an ordinary field work?

Yes, it's required, because there can be class methods as well not to mention that there can be type and const sections.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8038
Re: Class and normal (per-instance) fields.
« Reply #6 on: June 25, 2024, 09:45:17 pm »
Yes, it's required, because there can be class methods as well not to mention that there can be type and const sections.

Thanks for that, which I think fits in with Jamie's interpretation. It turned out that this form

Code: Pascal  [Select][+][-]
  1. TTicket= class
  2. public class
  3.   var InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  4.   var RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  5. public
  6. ...
  7.   end;
  8.  

was problematic, and in the end I've written it as

Code: Pascal  [Select][+][-]
  1.   TTicket= class
  2.     class var InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  3.     class var RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  4.   public                                (* This is needed to close var section  *)
  5.     Timestamp: TDateTime;
  6. ...
  7.  

and to Hell with the verbosity :-)

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

jamie

  • Hero Member
  • *****
  • Posts: 6735
Re: Class and normal (per-instance) fields.
« Reply #7 on: June 25, 2024, 10:50:19 pm »
Code: Pascal  [Select][+][-]
  1. TTicket= class
  2. public
  3.  class
  4.    Var
  5.      InfieldDegs: TVertexDegs; (* Used for range calculation           *)
  6.      RadioDegs: TVertexDegs;   (* Used for slant calculation           *)
  7. public
  8. ...
  9.   end;
  10.  

Like that  :D
The only true wisdom is knowing you know nothing

MarkMLl

  • Hero Member
  • *****
  • Posts: 8038
Re: Class and normal (per-instance) fields.
« Reply #8 on: June 25, 2024, 11:22:24 pm »
I see: the duplicated var.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018