Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi TObjectList-Object da, aber beim Zugriff Stack-Overflow (https://www.delphipraxis.net/187942-tobjectlist-object-da-aber-beim-zugriff-stack-overflow.html)

Captnemo 18. Jan 2016 14:27

TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Hi,

ich habe eine TObjectList nach diesem Schema:
Delphi-Quellcode:
type
  TTemperatur=class
  private
    FTemp: Extended;
    FId: string;
    procedure SetId(const Value: string);
    procedure SetTemp(const Value: Extended);
  public
    constructor Create;
    procedure WriteToStream(st: TStream);
    procedure LoadFromStream(st: TStream);
  published
    property Id: string read FId write SetId;
    property Temp: Extended read FTemp write SetTemp;
  end;

type
  TTemperaturList=class(TObjectList)
    function getItem(index: Integer): TTemperatur; virtual;
    procedure setItem(index: Integer; Temperatur: TTemperatur); virtual;
  public
    property Items[index: Integer]: TTemperatur read getItem write setItem; default;
    procedure Insert(index: Integer; Temperatur: TTemperatur); virtual;
    function Add(Temperatur: TTemperatur): Integer; virtual;
    function Remove(Temperatur: TTemperatur): Integer; virtual;
    function IndexOf(Temperatur: TTemperatur): Integer; virtual;
    function First: TTemperatur; virtual;
    function Last: TTemperatur; virtual;
    procedure WriteToStream(st: TStream);
    procedure LoadFromStream(st: TStream);
    function IndexOfId(value: string): Integer; virtual;
  end;

type
  TRaspberry=class
  private
    FName: string;
    FTemp: TTemperaturList;
    FIP: string;
    procedure SetIP(const Value: string);
    procedure SetName(const Value: string);
    procedure SetTemp(const Value: TTemperaturList);
  public
    constructor Create;
    destructor Destroy;
    procedure WriteToStream(st: TStream);
    procedure LoadFromStream(st: TStream);
  published
    property Name: string read FName write SetName;
    property IP: string read FIP write SetIP;
    property Temp: TTemperaturList read FTemp write SetTemp;
  end;

type
  TRaspberryList=class(TObjectList)
    function getItem(index: Integer): TRaspberry; virtual;
    procedure setItem(index: Integer; Raspberry: TRaspberry); virtual;
  public
    property Items[index: Integer]: TRaspberry read getItem write setItem; default;
    procedure Insert(index: Integer; Raspberry: TRaspberry); virtual;
    function Add(Raspberry: TRaspberry): Integer; virtual;
    function Remove(Raspberry: TRaspberry): Integer; virtual;
    function IndexOf(Raspberry: TRaspberry): Integer; virtual;
    function First: TRaspberry; virtual;
    function Last: TRaspberry; virtual;
    procedure WriteToFile(Filename: string);
    procedure LoadFromFile(Filename: string);
    function IndexOfIP(value: string): Integer; virtual;
  end;
Explizit geht es um die Funktion:
Delphi-Quellcode:
function TTemperaturList.IndexOfId(value: string): Integer;
var
  I: Integer;
begin
  Result:=-1;
  for I := 0 to self.Count-1 do
    if self[I].Id=value then
    begin
      Result:=I;
      Exit;
    end;
end;
der TTemperaturList.

Jetzt sollen mit folgender Prozedur TTemperatur-Items hinzugefügt, bzw. wenn sie schon vorhanden sind, geändert werden:
Delphi-Quellcode:
procedure TfrmMain.btn1Click(Sender: TObject);
var
  r, t: integer;
  Raspberry: TRaspberry;
  Temp: TTemperatur;
  FIP, FId: string;
  FTemperatur: Extended;
begin
  FIP:='192.168.0.110';
  FId:='28-000444e3ba3ff';
  FTemperatur:=19.937;
  r:=FRaspberrys.IndexOfIP(FIP);
  if r>-1 then
  begin
    Raspberry:=FRaspberrys[r];
  end else begin
    Raspberry:=TRaspberry.Create;
    FRaspberrys.Add(Raspberry);
  end;
  if Raspberry<>nil then
  begin
    t:=Raspberry.Temp.IndexOfId(FId); //<---Hier tritt beim zweiten durchlauf dann die Exception auf.
    if t>-1 then
    begin
      Temp:=Raspberry.Temp[t];  
    end else begin
      Temp:=TTemperatur.Create;
      Raspberry.Temp.Add(Temp);
    end;
    if Temp<>nil then
    begin
      Temp.Id:=FId;
      Temp.Temp:=FTemperatur;
    end;
  end;
end;
Ein TRaspberry wird manuell erzeugt, ist also vorhanden.

Beim TTemperaturList.IndexOfId(value: string) kommt es beim zweiten Aufruf der btn1Click zu einem Stackoverflow. Klar beim ersten mal wird das neue Objekt TTemperatur angelegt, weil eben noch keines vorhanden ist.
Beim zweiten Mal müsste er es ja finden, da die Id die gleich ist. Komischerweise wird in der IndexOfId als Count auch 1 ausgegeben, aber auf das Object kann ich nicht zugreifen. Also irgendwo hab ich einen Denkfehler, aber ich finde ich einfach nicht.

mjustin 18. Jan 2016 14:43

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von Captnemo (Beitrag 1327332)
ich habe eine TObjectList nach diesem Schema:

Einen Grund für den Absturz konnte ich noch nicht sehen. Vielleicht können wir es besser reproduzieren wenn der Code auf ein minimales Beispiel reduziert wird?

Robuster wäre es die Containerklasse nicht von der Klasse TObjectList abzuleiten sondern eine interne (private) TObjecList Instanz zu benutzen. Das entkoppelt die API der TObjectList und sichert den Client-Code besser gegen "versehentliche" Verwendung von TObjectList Methoden ab. Das könnte auch für die konkrete Fehlersuche hilfreich sein.

Captnemo 18. Jan 2016 14:47

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Danke für deine Hilfe. Den eigentlichen Fehler habe ich mittlerweile gefunden: es war lediglich ein fehlendes inherited beim Zugriff auf das TTemperaturobjekt.

Zitat:

Zitat von mjustin (Beitrag 1327335)
wäre es die Containerklasse nicht von der Klasse TObjectList abzuleiten sondern eine interne (private) TObjecList Instanz zu benutzen. Das entkoppelt die API der TObjectList und sichert den Client-Code besser gegen "versehentliche" Verwendung von TObjectList Methoden ab. Das könnte auch für die konkrete Fehlersuche hilfreich sein.

Aber wenn du mir das noch mal anders erläutern könntest, wäre ich dir sehr dankbar, denn ich weiß im Augenblick nicht was du damit meinst.

mjustin 18. Jan 2016 14:53

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Bei mir sind Container immer z.B. so aufgebaut:

Delphi-Quellcode:
type
  TTemperaturList=class(TObject)
  private
    Liste: TObjectList;

    ...
   
  public
    constructor Create;
    destructor Destroy; override;
Von aussen hin kann ich dann gezielt nur die von meiner Klasse definierten Funktionen und Prozeduren aufrufen. Dass innen eine TObjectList oder etwas anderes enthalten ist, soll der Client nicht sehen können.

Jumpy 18. Jan 2016 14:57

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Ich denke er meint, dass du eine Objektlist in deiner neuen Klasse kapselst. So kann man ganz gut die eventuelle Object-Casterei verbergen.

Edit:Und war sogar selber schneller als ich...

Delphi-Quellcode:
type
  TTemperaturList=class
    ObjectList:TObjectList;
  public
    constructor create;
    function count:integer;
  end;

TTemperaturList.create;
begin
  ObjectList:=TObjectList.create;
end;

TTemperaturList.count:integer;
begin
  Result:=ObjectList.Count;
end;

Andere Bemerkung, wenns erlaubt ist: Was mich an deinem Klassenkonstrukt stört ist was für verschiedene Dinge nicht alles Temp heißen. Da hat ich nämlich auch Probleme deinen QT nachzuvollziehen.

mjustin 18. Jan 2016 15:05

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Delphi-Quellcode:
type
  TTemperaturList=class(TObjectList)
    function getItem(index: Integer): TTemperatur; virtual;
    procedure setItem(index: Integer; Temperatur: TTemperatur); virtual;
  public
    property Items[index: Integer]: TTemperatur read getItem write setItem; default;
    procedure Insert(index: Integer; Temperatur: TTemperatur); virtual;
    function Add(Temperatur: TTemperatur): Integer; virtual;
    function Remove(Temperatur: TTemperatur): Integer; virtual;
    ...
Die Methoden getItem und setItem brauchen nicht als published declariert werden, eventuell fehlt hier das private. Published wird nicht benötigt ausser man möchte die Klasse über das Delphi DFM Streaming oder klassische RTTI ansprechen.

SProske 18. Jan 2016 15:12

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Da der TE wohl XE4 verwendet, wäre sicher auch die generische Objektliste aus der Unit System.Generics.Collections einen Blick Wert.
Damit könnte man sich die Schreibarbeit für Add/Remove/IndexOf etc. ersparen.

Captnemo 18. Jan 2016 15:25

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
@mjustin:
das Private fehlt tatsächlich. Eigentlich kommen bei mir die Getter und Setter immer in Private, hatte ich lediglich vergessen. (hätte bei dem fehlenden Inherited aber nichts geändert).
Aber ich weiß jetzt was du meinst.

Klar kann man das auch über Generics verwenden. Aber ich habe mich so an meine ObjectList gewöhnt....

Und, zugegeben, der Variablenname Temp ist sicherlich nicht der schlaueste, aber man kann das schon lesen ;)

Sir Rufo 18. Jan 2016 15:50

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Nur so als Tip:

Wenn man Klassen mit Stream-Funktionen ausstattet, dann sollte man auch immer das Interface Delphi-Referenz durchsuchenIStreamPersist an die Klasse heften.

Macht auf jeden Fall das Leben leichter ;)

freimatz 18. Jan 2016 16:15

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von Captnemo (Beitrag 1327338)
Zitat:

Zitat von mjustin (Beitrag 1327335)
wäre es die Containerklasse nicht von der Klasse TObjectList abzuleiten sondern eine interne (private) TObjecList Instanz zu benutzen. Das entkoppelt die API der TObjectList und sichert den Client-Code besser gegen "versehentliche" Verwendung von TObjectList Methoden ab. Das könnte auch für die konkrete Fehlersuche hilfreich sein.

Aber wenn du mir das noch mal anders erläutern könntest, wäre ich dir sehr dankbar, denn ich weiß im Augenblick nicht was du damit meinst.

Siehe auch:
http://clean-code-developer.de/die-g...heritance_FCoI

Benmik 2. Feb 2016 22:54

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von Sir Rufo (Beitrag 1327352)
Wenn man Klassen mit Stream-Funktionen ausstattet, dann sollte man auch immer das Interface Delphi-Referenz durchsuchenIStreamPersist an die Klasse heften.Macht auf jeden Fall das Leben leichter ;)

Könntest du mal ausführen, in welchen praktischen Fällen dieses Interface das Leben leichter machen würde? Habe mich ein bisschen in Interfaces eingelesen und bin leicht ratlos, welchen Nutzen man eigentlich von Interfaces hat, wenn man nicht gerade komplexe Klassenstrukturen hat oder externe Schnittstellen ansprechen will.

stahli 3. Feb 2016 11:30

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Hi Benmik,

ich habe mal ein kleines Einsteigertutorial für Interfaces erstellt.
Vielleicht hilft es Dir ja etwas: http://www.delphipraxis.net/183702-i...-factorys.html

Interfaces sind hilfreich, wenn man gleiche Funktionalitäten in unterschiedlichen Klassen unterbringen will.
Es erspart einem das Prüfen und Casten von vorliegenden Objekten wie
Delphi-Quellcode:
if (MyObj is ClassA) then
  (MyObj as ClassA).DoX
else if (MyObj is ClassB) then
  (MyObj as ClassB).DoX;
Statt dessen schreibt man einfach
Delphi-Quellcode:
var
lDoX: IDoX;
...
if Supports(MyIntf, IDoX, lDoX)
  lDoX.DoX;
Dan ist völlig egal, was da für ein Objekt dahinter steckt. Wichtig ist nur noch, ob DoX unterstützt wird oder nicht.


Fluch und Segen (und außerhalb von Delphi unüblich) ist die automatische Referenzzählung. Objekte hinter Schnittstellen werden automatisch freigegeben, wenn es keine Referenzen mehr darauf gibt.
Das kann man als positiv oder auch negativ bewerten - je nach den vorliegenden Bedürfnissen.

Benmik 3. Feb 2016 11:58

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Hallo Stahli, deine Produktionen waren für mich sozusagen erste Anlaufstelle. Ich habe zum ersten Mal im Groben kapiert, was Interfaces überhaupt sind. Jetzt stehe ich vor der moralischen Entscheidung :? : Muss ich die auch verwenden? Die meisten Tutorial-Autoren machen eine Bemerkung, dass für "einfache" Delphi-Programme Interfaces meist nicht notwendig sind. Deshalb hat mich Sir Rufos Bemerkung verwirrt: Warum sollte mann immer das Interface "anheften"? Ist das wirklich die Regel, dass man so viele Objekte hat, die man unter einem Interface versammeln muss?
Und was ich über die Tücken der Referenzzählung gelesen habe: Da sträuben sich die Haare. Also, selbst wenn ich drei Objekte hätte, ich würde die Klassen einzeln erstellen und gut ist. Voraussetzung natürlich: Ich habe weder Schnittstellen nach außen noch irgendwelche Plugins oder Ähnliches.
Aber vielleicht übersehe ich da was. Daher die Bitte um eine praktische Verwendung.

Sir Rufo 3. Feb 2016 12:11

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von stahli (Beitrag 1329223)
Fluch und Segen (und außerhalb von Delphi unüblich) ist die automatische Referenzzählung. Objekte hinter Schnittstellen werden automatisch freigegeben, wenn es keine Referenzen mehr darauf gibt.
Das kann man als positiv oder auch negativ bewerten - je nach den vorliegenden Bedürfnissen.

Das ist leider total falsch.
  1. Die Referenz-Zählung ist keine Erfindung von Delphi
  2. Eine Instanz kann sich automatisch freigeben, wenn der Referenz-Zähler auf 0 geht. Ob das passiert hängt von der Implementierung ab ... und die hat man ja selber in der Hand.

Benmik 3. Feb 2016 12:26

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Und könntest du neben Stahli auch mich mit einer Antwort bedenken?

Sir Rufo 3. Feb 2016 12:31

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von Benmik (Beitrag 1329165)
Zitat:

Zitat von Sir Rufo (Beitrag 1327352)
Wenn man Klassen mit Stream-Funktionen ausstattet, dann sollte man auch immer das Interface Delphi-Referenz durchsuchenIStreamPersist an die Klasse heften.Macht auf jeden Fall das Leben leichter ;)

Könntest du mal ausführen, in welchen praktischen Fällen dieses Interface das Leben leichter machen würde? Habe mich ein bisschen in Interfaces eingelesen und bin leicht ratlos, welchen Nutzen man eigentlich von Interfaces hat, wenn man nicht gerade komplexe Klassenstrukturen hat oder externe Schnittstellen ansprechen will.

Wenn du da so eine schöne Klasse hast, die sich in einen Stream schreiben kann, dann könnte man eines schönen Tages auf die Idee kommen, diese in einer Datenbank in einem Blob-Feld abzulegen.

Habe ich damals beim designen der Klasse nachgedacht und das Interface
Delphi-Quellcode:
IStreamPersist
implementiert, dann sieht mein Code so aus
Delphi-Quellcode:
MyDataSet.Append;
MyDataSet.FieldByName( 'BlobData' ).Assign( MyInstance );
MyDataSet.Post;
Und wenn ich es wieder zurück haben möchte?
Delphi-Quellcode:
// leider nur sehr kurz
MyInstance.Assign( MyDataSet.FieldByName( 'BlobData' ) );

Benmik 3. Feb 2016 12:44

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Hab Dank. Das muss ich mir erstmal zu Gemüte führen. Vermutlich liegt für mich der Punkt, an dem ich mal Interfaces brauchen kann, in einiger Ferne. Das war bei generischen Objektlisten allerdings auch mal so, und heute kann ich mir ein Leben ohne sie gar nicht mehr vorstellen. :thumb:

stahli 3. Feb 2016 13:01

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
@Benmik

Also grundsätzlich würde ich bei einem überschaubaren Projekt keine Interfaces verwenden. Erst mal bringt das Dir nämlich nichts.

Interfaces bringen Dir nur etwas, wenn sie Vorteile bringen. ;-)

Wenn die Wahrscheinlichkeit besteht, dass Du später verschiedene Klassen dynamisch tauschen möchtest, dann machen Interfaces (und der etwas höhere Aufwand) Sinn. Man kann dann eher in "Funtionalitäten" denken und die Klassen sind dann "Funktionalitätenblöcke" (IkannA, IkannB, IkannC).

Wenn Du mit Klassen nicht mehr weiter kommst und andauernd casten musst, dann denke halt über den Einsatz von Interfaces nach. Als reinen Selbstzweck macht das wenig Sinn.


@Sir Rufo
Oh, ich sehe gerade, man kann Interfaces offenbar auch TObject zuweisen und nicht nur TInterfacedObject und somit ohne Referenzzählung arbeiten. War mir noch gar nicht klar. Werde ich heute Abend gleich mal versuchen.
Ich dachte es geht nur mit (Leer-)Überschreiben der Referenzzählung.

Sir Rufo 3. Feb 2016 13:12

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Mit einem Interface legt man fest, was die Klasse implementieren muss.

Basis für jedes Interface ist dabei
Delphi-Quellcode:
IInterface
und das legt 3 Methoden fest, die also immer wie auch immer vorhanden sein müssen.

Wie diese Implementierung und das Verhalten aussieht ... bleibt dem Klassen-Designer überlassen.

Delphi-Quellcode:
TInterfacedObject
hat diese 3 Methoden schon implementiert und ich kann von da meine Klasse ableiten ... wenn die Implementierung von
Delphi-Quellcode:
TInterfacedObject
auch passt.

Und es gibt noch eine Menge mehr Spielarten gerade im Bezug auf Interfaces, die das ganze dann auch sehr flexibel machen.

stahli 3. Feb 2016 13:22

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Stimmt, ich hatte mich hier von einem anderen Thread in die Irre führen lassen. Aber Das Überschreiben (Stillegen) der Referenzzählung werde ich nochmal versuchen.

Sir Rufo 3. Feb 2016 13:24

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Zitat:

Zitat von stahli (Beitrag 1329249)
Stimmt, ich hatte mich hier von einem anderen Thread in die Irre führen lassen. Aber Das Überschreiben (Stillegen) der Referenzzählung werde ich nochmal versuchen.

Einfach von Delphi-Referenz durchsuchenTInterfacedPersistent ableiten

Benmik 3. Feb 2016 14:17

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
Naja, die Delphi-Gurus drohen mit Exkommunikation, wenn man sich nicht an ihre Bibeln hält (Nick Hodges: "If I could teach a new programmer one thing it would be this: Program to an interface, not an implementation."; "A good developer codes against abstractions, and not implementations."). Da ist es ein Vorteil, wenn man kein zertifizierter Delphi-Priester ist: Man kommt nicht in den Olymp, kann aber auch nicht auf dem Scheiterhaufen landen. :wink:

Sir Rufo 3. Feb 2016 15:14

AW: TObjectList-Object da, aber beim Zugriff Stack-Overflow
 
@Benmik

Mach dir da nichts draus. Der Nick versteckt die implementierenden Klassen auch gerne mal im
Delphi-Quellcode:
implementation
Teil und erzeugt Instanzen im
Delphi-Quellcode:
initialization
Abschnitt.

Da kenne ich wiederum einige, die ihm dafür die Büßer-Kutte umhängen würden. :stupid:
(Ja, ich auch)


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:01 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz