Grundsätzliches zu Objekt/ObjektList

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
NoCee
Beiträge: 170
Registriert: Do 3. Mär 2011, 21:34
OS, Lazarus, FPC: WinXp/7/10 Opensuse13.2/Leap15.3 (L 2.2.0 FPC 3.2.2 )
CPU-Target: Intel 32/64Bit, ARM9
Wohnort: Ulm

Grundsätzliches zu Objekt/ObjektList

Beitrag von NoCee »

Hallo zusammen,
Ich versuche gerade ein Projekt zu verwirklichen, daß von Objekten/Objektlisten
gebrauch macht. Da ich nach reichlich Internetrecherche viele offene Fragen dazu habe
würde ich gerne die wichtigsten von meiner Liste streichen.
Die aktuell verwendeten Objekte haben momentan nur ein paar strings.
Eine Objektlist ist global eine lokal definiert.

Code: Alles auswählen

Bufferobj:= TMyRecObj.Create;// lokale Variable
Bufferobj.MyRecord.ScanID:= 123;
BufferObjList.Add(Bufferobj);// BufferObjList ist eine globale Variable
Bufferobj.MyRecord.ScanID:= 456;
BufferObjList.Add(Bufferobj);
Hab ich jetzt 1 Objekt und 3 darauf zeigende Pointer oder 3 Objekte?
Wenn ich die procedure verlasse, muß ich die lokale Variable mit free zerstören
oder wird die, weil lokal, sowieso zerstört.

Code: Alles auswählen

Bufferobj:= TMyRecObj(bufferobjlist[0]);// Bufferobj ist vom Typ TMyRecObj
Was wird kopiert, Feldinhalte oder Pointer?
Behält die Liste das Objekt?

Code: Alles auswählen

for i:= 0 to 5 do 
begin
   MyObjList2[i+5] := MyObjList1[i];
end;
Objektlist2 soll eine Teilmenge von Objektlist1 sein.
Werden hier nur die Pointer kopiert oder die Inhalte der Felder
ich brauche 2 unabhängig bearbeitbare Listen (echte Kopie).
Oder muß ich da für den Inhalt jedes Feld separat kopieren?
Die globale Objektliste soll im ursprünglichem Zustand erhalten bleiben,
die lokale Objektliste soll sortiert und bearbeitet werden. Zum Schluß landet diese dann in einem Stringgrid.

Ich hab versucht, einiges in einem Testprogramm auszuprobieren, bekomme es aber nicht wirklich zum Laufen.
Das liegt halt daran, daß ich nicht genau weiß was ich da tue.
Was ich grundsätzlich auch nie wirklich geschnallt habe, wann wird bei Variablen kopiert, wann nur der Pointer
so daß eine von 2 Variablen im Speichernirvana verschwindet.
Ich hoffe jetzt auf erhellende Antworten von euch.

Danke schon mal im Voraus
Gruß
NoCee

Benutzeravatar
theo
Beiträge: 10498
Registriert: Mo 11. Sep 2006, 19:01

Re: Grundsätzliches zu Objekt/ObjektList

Beitrag von theo »

Mal kurz als Überblick, es gibt nachher bestimmt noch ausführlichere Erklärungen.

zu den Snippets:
1: Du hat nur so viele Objekte, wie du createst, also 1.
ScanID wird einfach überschrieben.
BufferObj ist erst frei, wenn du es freigibst, bzw. wenn OwnsObjects true ist: https://www.freepascal.org/docs-html/fc ... jects.html

2 und 3: Pointer.

Warf
Beiträge: 1912
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Grundsätzliches zu Objekt/ObjektList

Beitrag von Warf »

Eine Klasseninstanz (ich vermeide den Begriff Objekt das es noch das Konzept von "objects" in Pascal gibt die anders funktionieren als Klassen), ist immer eine Referenz. Jedwede zweisung und übergabe ist die Übergabe der Referenz.
Wenn du also eine Klasseninstanz erzeugst und die in 2 Listen abspeicherst, sind das 2 mal referenzen zu der selben Instanz. Wenn du die Instanz zerstörst, sind beide Referenzen invalide.

Da du in Pascal für Klasseninstanzen deinen Speicher selbst verwalten darfst, haben viele Strukturen ein so genanntes Owner Prinzip. Das findet sich z.B. in TObjectList, was eine liste ist die nicht nur Klasseninstanzen halten kann, sondern diese auch noch besitzen kann (wird über einen Parameter im Konstruktor gesteuert). Wenn du also eine Klasseninstanz einer TObjectList die als Owner fungiert übergibst, dann wird diese die Lebenszeit des Objekts managen. D.h. wenn du die ObjectList clearst, Freest, oder ein Element löschst (mit Delete/Remove) dann wird die Darin enthaltene Klasseninstanz ge-freed. Wenn du das nicht willst kannst du Extract benutzen.

Wenn du also 2 listen haben willst von denen eine ein subset von der anderen ist, dürfen nicht beide Owner sein. Am besten verwendest du hierfür auch Generische Listen, dann sparst du dir das rumgecaste:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}

uses
  classes,
  Generics.Collections;

type
  TSLOwnerList = class(specialize TObjectList<TStringList>);
  TSLList = class(specialize TList<TStringList>);

var
  OwnerList: TSLOwnerList;
  SubSetList: TSLList; // Alternatively: TSLOwnerList and then call TSLOwnerList.Create(False) to not own the objects
begin
  OwnerList := TSLOwnerList.Create; // Ownsobjects true by default
  SubSetList := TSLList.Create; // not a TObjectList, so cannot own objects
  try
    OwnerList.Add(TStringList.Create);
    SubSetList.Add(OwnerList[0]);
    SubSetList[0].Add('Hello World');
    WriteLn(OwnerList[0].Text);
  finally
    SubSetList.Free; // won't free any objects it contains
    OwnerList.Free; // Will also free all objects that it contains because it owns them
  end;
end.

Benutzeravatar
theo
Beiträge: 10498
Registriert: Mo 11. Sep 2006, 19:01

Re: Grundsätzliches zu Objekt/ObjektList

Beitrag von theo »

Ich würde hier auch mit generischen Listen arbeiten.
Da ich mal vermute, dass dein TMyRecObj dem Namen nach eine Notlösung ist um einen Record in eine Liste zu bringen, könntest du es auch so machen:

Code: Alles auswählen

unit Unit1;

{$mode objfpc}{$H+}
{$MODESWITCH AdvancedRecords+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, fgl;

type

  { TSrchResult }

  TMyRecord = record
    ScanID: integer;
    AName: string;
    class operator = (Left, Right: TMyRecord): boolean;  //AdvancedRecords
  end;

  TMyRecordList = specialize TFpgList<TMyRecord>;

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  ARecordList: TMyRecordList;
  ARec: TMyRecord;
begin
  ARecordList := TMyRecordList.Create;

  ARec.ScanID := 1;
  ARec.AName := 'eins';
  ARecordList.Add(ARec);
  ARec.ScanID := 2;
  ARec.AName := 'zwei';
  ARecordList.Add(ARec);
  ARec.ScanID := 3;
  ARec.AName := 'drei';
  ARecordList.Add(ARec);

  ShowMessage(ARecordList[0].AName + ARecordList[1].AName + ARecordList[2].AName);
  ARecordList.Free;
end;

{ TSrchResult }

//Dummy. EInfach überlesen ;-)
//https://gitlab.com/freepascal.org/fpc/source/-/issues/15480
class operator TMyRecord. = (Left, Right: TMyRecord): boolean;
begin
  Result := (Left.ScanID > Right.ScanID); 
end;

end.    
Hier ist jeder Eintrag ein Record, also kein Pointer.

Warf
Beiträge: 1912
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Grundsätzliches zu Objekt/ObjektList

Beitrag von Warf »

Wenn man Generics.Collections TList benutzt statt fgl's TFPGList, muss man keinen Gleicheitsoperator mehr überladen. Damit funktionieren auch non advanced records ohne modifikation

Benutzeravatar
theo
Beiträge: 10498
Registriert: Mo 11. Sep 2006, 19:01

Re: Grundsätzliches zu Objekt/ObjektList

Beitrag von theo »

Warf hat geschrieben:
Mo 13. Mär 2023, 16:49
Wenn man Generics.Collections TList benutzt statt fgl's TFPGList, muss man keinen Gleicheitsoperator mehr überladen. Damit funktionieren auch non advanced records ohne modifikation
Sehr gut. Wieder was gelernt! :D

Damit wäre mein Beispiel von oben so:

Code: Alles auswählen

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Generics.Collections;

type

  { TSrchResult }

  TMyRecord = record
    ScanID: integer;
    AName: string;
  end;

  TMyRecordList = class(specialize TList<TMyRecord>);

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  ARecordList: TMyRecordList;
  ARec: TMyRecord;
begin
  ARecordList := TMyRecordList.Create;

  ARec.ScanID := 1;
  ARec.AName := 'eins';
  ARecordList.Add(ARec);
  ARec.ScanID := 2;
  ARec.AName := 'zwei';
  ARecordList.Add(ARec);
  ARec.ScanID := 3;
  ARec.AName := 'drei';
  ARecordList.Add(ARec);

  ShowMessage(ARecordList[0].AName + ARecordList[1].AName + ARecordList[2].AName);
  ARecordList.Free;
end;

end.

Antworten