property und default

Rund um die LCL und andere Komponenten
Antworten
Mathias
Beiträge: 6164
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

property und default

Beitrag von Mathias »

Ich will mir eine StringListe mit Color-Parameter bauen.
Die Methode Add funktioniert.

Aber bei GetItem und bei Destroy kommt ein SIGESV, wobei, wen die Liste keine Elemente hat, funktioniert auch Destroy.
Was noch interessant ist, bei Destroy, wird der SIGSEV auch bei GetItem ausgelöst. entferne ich aber die Self-Zeile dort, dannn kommt das Assembler Fenster.
Starte ich aber da ganze direkt von der Konsole, dann wird es ohne Fehler beendet.
Was noch ist, das SIGSEV, kommt nicht in der Result-Zeile, sondern bei "begin".

Ich denke, ich mache irgend etwas mit den Zeigern falsch, nur die Frage wo ?


Code: Alles auswählen

type
  PColorString = ^TColorString;
  TColorString = record
    s: string;
    col: TColor;
  end;
 
  { TColorStringList }
 
  TColorStringList = class(TList)
  private
    function GetItem(index: integer): TColorString;
    procedure SetItem(index: integer; AValue: TColorString);
  public
    property Items[index: integer]: TColorString read GetItem write SetItem; default;
    constructor Create;
    destructor Destroy; override;
 
    procedure Add(s: string; col: TColor = clGray);
  end;
 
implementation
 
{ TColorStringList }
 
function TColorStringList.GetItem(index: integer): TColorString;
begin
  Result := Self[index];
  //  Result.s:='fgfgfgd';  // Mit diesen 2 Zeilen geht es.
  //  Result.col:=clRed;
end;
 
procedure TColorStringList.SetItem(index: integer; AValue: TColorString);
begin
  Self[index] := AValue;
end;
 
constructor TColorStringList.Create;
begin
  inherited Create;
end;
 
destructor TColorStringList.Destroy;
var
  cs: PColorString;
  i: integer;
begin
  for i := 0 to Self.Count - 1 do begin
    cs^ := Self[i];
    Dispose(cs);
  end;
 
  inherited Destroy;
end;
 
procedure TColorStringList.Add(s: string; col: TColor);
var
  cs: PColorString;
begin
  New(cs);
  cs^.s := s;
  cs^.col := col;
  inherited  Add(cs);
end;     


Das HauptForm ist eigentlich nicht besonders:

Code: Alles auswählen

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    cs: TColorStringList;
  public
 
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  cs.Add('Hello World: ' + IntToStr(cs.Count), clRed);
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
  Caption := cs.Items[2].s;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  cs := TColorStringList.Create;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  cs.Free;
end;
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: property und default

Beitrag von m.fuchs »

Du machst da ganz schön Murks. Deine Items-Property benutzt die GetItem-Funktion. Diese wiederum greift auf die Items-Property zu. Wie soll das denn funktionieren?

Code: Alles auswählen

function TColorStringList.GetItem(index: integer): TColorString;
begin
  Result := TColorString(inherited Get(index)^);
end;

So wird etwas daraus.

Auch beim Destroy solltest du einfach die Get-Methode der Elternklasse benutzen:

Code: Alles auswählen

destructor TColorStringList.Destroy;
var
  cs: PColorString;
  i: integer;
begin
  for i := Self.Count - 1 downto 0 do begin
    cs := inherited Get(i);
    Dispose(cs);
  end;
  inherited Destroy;
end;



ABER: Was ich hier sehe, sagt mir nur wieder eins: benutze keine Pointer wenn es nicht unbedingt sein muss. Und schon gar nicht, wenn man nicht genau weiß was man tut.
Alternativen:
  1. Mach aus TColorString eine Klasse und benutze die TObjectList.
  2. Mach aus TColorString eine Klasse und benutze eine Spezialisierung von TFPGObjectList
  3. Vergiss TColorString und benutze eine Spezialisierung von TFPGMap mit einem String als Key und TColor als Data.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: property und default

Beitrag von wp_xyz »

m.fuchs hat das in seinem Beispiel bereits berücksichtigt, aber nicht kommentiert:

Die Ursache für die Probleme beim Destroy ist, dass die Schleife von unten nach oben durchlaufen wird. Da die obere Grenze der for-Schleife vor Schleifen-Eintritt bestimmt wird, ändert sie sich während des Löschens nicht mehr, während die Anzahl der Listeneinträge ständig abnimmt. Daher greifst du irgendwann auf nicht mehr existierende Elemente zu.

Abhilfe: von oben nach unten durchlaufen lassen, also: for i := Count-1 downto 0 ... - dann werden die bereits gelöschten Indices nicht mehr erreicht. Oder eine while-Schleife nehmen und immer das 0-te Element löschen.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: property und default

Beitrag von m.fuchs »

wp_xyz hat geschrieben:Die Ursache für die Probleme beim Destroy ist, dass die Schleife von unten nach oben durchlaufen wird. Da die obere Grenze der for-Schleife vor Schleifen-Eintritt bestimmt wird, ändert sie sich während des Löschens nicht mehr, während die Anzahl der Listeneinträge ständig abnimmt. Daher greifst du irgendwann auf nicht mehr existierende Elemente zu.

Das ist ein wichtiger Hinweis. Aber das dürfte in diesem Fall nicht das Problem sein, da der OP ja die Einträge nicht aus der Liste löscht. Es werden bloß die reservierten Speicherbereiche freigegeben, die Pointer zeigen aber noch immer dahin.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Mathias
Beiträge: 6164
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: property und default

Beitrag von Mathias »

1. Mach aus TColorString eine Klasse und benutze die TObjectList.
2. Mach aus TColorString eine Klasse und benutze eine Spezialisierung von TFPGObjectList

Dann würde es aus jedem String ein Klasse geben, braucht dies nicht sehr viel Overhead, oder täusche ich mich da ?


Was sind die TFGP* für Klassen, habe nie etwas davon gehört ?


Es werden bloß die reservierten Speicherbereiche freigegeben, die Pointer zeigen aber noch immer dahin.

Das würde es wohl reichen, noch ein NIL zu vergeben ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: property und default

Beitrag von wp_xyz »

colorstringlist.zip
(2.67 KiB) 35-mal heruntergeladen
@ m.fuchs: Hast recht - da habe ich mal wieder nicht genau genug hingesehen.

@Mathias: In der Anlage findest du ein ausgearbeites Beispiel wie man die TColorStringList mit Records realisieren könnte. Wichtig ist noch, dass die TList Pointer speichert (also PColorString), keine Records (TColorString). Daher dürfen die Getter/Setter von Items nicht TColorString verwenden, sondern PColorString.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: property und default

Beitrag von m.fuchs »

Mathias hat geschrieben:
1. Mach aus TColorString eine Klasse und benutze die TObjectList.
2. Mach aus TColorString eine Klasse und benutze eine Spezialisierung von TFPGObjectList

Dann würde es aus jedem String ein Klasse geben, braucht dies nicht sehr viel Overhead, oder täusche ich mich da ?

Mehr Overhead als bei der Reservierung von Speicher für deine Records? Das kann soviel nicht sein. Abgesehen davon: ist deine Anwendung extrem zeitkritisch oder muss sie maximal speichersparend sein?

Mathias hat geschrieben:Was sind die TFGP* für Klassen, habe nie etwas davon gehört ?

Das sind Generics, Vorlageklassen die man für eigene Anwendungsfälle spezialisiert.

In TObjectList kann man ja beliebige Object vom Typ TObject (oder Ableitungen davon) verwalten. Man kann aber auch TFPGObjectList spezialisieren:

Code: Alles auswählen

TColorStringList = specialize TFPGObjectList<TColorString>;
Jetzt kommen da nur noch Objekte vom Typ TColorString (oder Ableitungen davon) hinein.

TFPGMap ist die generische Basisklasse für HashMaps. Die funktionieren im Key-Data-Verfahren. Beispiel:

Code: Alles auswählen

TStringColorMap = specialize TFPGMap<String, TColor>;
Jetzt hast du eine Map in der du Elemente von TColor verwalten kannst, die per String indiziert sind.
Du kannst Elemente hinzufügen:

Code: Alles auswählen

MyMap.Add('Rot', clRed);
MyMap.Add('Blau', clBlue)
Und auf sie zugreifen:

Code: Alles auswählen

Form1.Color := MyMap['Rot'];


Könnte ja vielleicht auch eine Lösung für deine Aufgabestellung sein.

Mathias hat geschrieben:
Es werden bloß die reservierten Speicherbereiche freigegeben, die Pointer zeigen aber noch immer dahin.

Das würde es wohl reichen, noch ein NIL zu vergeben ?

Nein, sauber wäre es in der for...downto-Schleife nach dem Aufräumen des Speicherbereichs noch ein .Delete(i) zu machen. Dann ist das Element wirklich aus der Liste verschwunden. Bei nil wäre es immer noch drin.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Mathias
Beiträge: 6164
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: property und default

Beitrag von Mathias »

Du machst da ganz schön Murks. Deine Items-Property benutzt die GetItem-Funktion. Diese wiederum greift auf die Items-Property zu. Wie soll das denn funktionieren?

Ich hatte wohl ein Brett vor den Augen. :cry:

Ich bin noch auf eine Lösung, mit dem Umweg über cs gekommen.
Bei SetItem und Destroy bin ich über den gleichen Fehler gestolpert.
Aber deine Lösung mit mit Get und einer Typenumwandlung ist natürlich viel eleganter. Danke.

Mein Code habe ich nun angepasst und scheint nun zu funktionieren.

Code: Alles auswählen

function TColorStringList.GetItem(index: integer): TColorString;
var
  cs: PColorString;
begin
  Result := TColorString(inherited Get(index)^);
 
  //  cs := inherited Items[index];
  //  Result:=cs^;
 
  //   Result :=  inherited Items[index]^;
end;
 
procedure TColorStringList.SetItem(index: integer; AValue: TColorString);
var
  cs: PColorString;
begin
  cs := inherited Items[index];
  cs^ := AValue;
end;
 
destructor TColorStringList.Destroy;
var
  cs: PColorString;
  i: integer;
begin
  for i := 0 to Self.Count - 1 do begin
    cs := inherited Items[i];
    Dispose(cs);
  end;
 
  inherited Destroy;
end;
 
function TColorStringList.Add(s: string; col: TColor): integer;
var
  cs: PColorString;
begin
  New(cs);
  cs^.s := s;
  cs^.col := col;
  Result := inherited Add(cs);
end;


@Mathias: In der Anlage findest du ein ausgearbeites Beispiel wie man die TColorStringList mit Records realisieren könnte. Wichtig ist noch, dass die TList Pointer speichert (also PColorString), keine Records (TColorString). Daher dürfen die Getter/Setter von Items nicht TColorString verwenden, sondern PColorString.

Ich habe mir den Code angeguckt. Aber dann muss ich im Form auch mit Pointer arbeiten, aber das will ich wen möglich umgehen.

Im Prinzip könnte ich das Ganze auch mit einer Verketten-Liste machen, aber ich denke mit TList baut man weniger Fehler ein. :wink:

Mathias hat geschrieben:
Es werden bloß die reservierten Speicherbereiche freigegeben, die Pointer zeigen aber noch immer dahin.

Das würde es wohl reichen, noch ein NIL zu vergeben ?

Nein, sauber wäre es in der for...downto-Schleife nach dem Aufräumen des Speicherbereichs noch ein .Delete(i) zu machen. Dann ist das Element wirklich aus der Liste verschwunden. Bei nil wäre es immer noch drin
Wird die List nicht von Hause auf sauber aufgeräumt, so wie es bei einer StringList der Fall ist ? Das man aber die einzelnen Elemente Disposen muss ist mir klar.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6164
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: property und default

Beitrag von Mathias »

@Mathias: In der Anlage findest du ein ausgearbeites Beispiel wie man die TColorStringList mit Records realisieren könnte. Wichtig ist noch, dass die TList Pointer speichert (also PColorString), keine Records (TColorString). Daher dürfen die Getter/Setter von Items nicht TColorString verwenden, sondern PColorString.

Jetzt habe ich das Problem erkannt.

Die Ausgabe ist dann nicht mehr optimal.
Bei der Variante 1 muss ich 2x Get aufrufen.
Und bei der 2. gibt es eine Kopie des Stringes.

Code: Alles auswählen

procedure TForm1.PrintClick(Sender: TObject);
var
  s: TColorString;
  i: integer;
begin
// Variante 1
  for i := 0 to cs.Count - 1 do begin
    Canvas.Font.Color := cs.Items[i].col;
    Canvas.TextOut(0, i * 20, cs.Items[i].s);
  end;
 
// Variante 2
  for i := 0 to cs.Count - 1 do begin
    s := cs.Items[i];
    Canvas.Font.Color := s.col;
    Canvas.TextOut(0, i * 20, s.s);
  end;
end;
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten