Entwickler-Ecke

Sonstiges (Delphi) - Selbstzerstörendes Klassen-Kind


Symbroson - Mo 23.04.18 10:09
Titel: Selbstzerstörendes Klassen-Kind
Hallo EE,

Ich bin gerade dabei eine Vektor- bzw. Matrix-Klasse für den Info-Unterricht zu schreiben. Leider gibt es ja kein Operator-Overloading bei Delphi7 Klassen, also behelfe ich mich mit add,mul,scalar,cross usw. Methoden, die als Argument einen anderen Vektor erhalten.

Wenn man allerdings einen Vektor nur temporär für eine Rechenoperation braucht müsste man sich ja erst ein Vektor-Objekt erstellen, und kann es später nicht wieder freigeben, weil man ja nicht ständig neue variablen dafür erstellen will.
Also habe ich die Methoden überladen um anstatt dem Vektor die einzelnen x,y,z-Werte zu erwarten.

Jetzt stellt sich mir die Frage, ob man evtl irgendwie erreichen kann, dass das Objekt später wieder freigegeben wird, wenn es keiner Variable zugewiesen wurde. oder irgendwie so. :nixweiss:

Beispiel:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var v1 : TVec3;
 
begin
    v1 := TVec3.Create(1,2,3);
    v1.add(4,5,6);        //okay
    //v1.add(TVec3(7,8,9)); //memory-Leak
    //edit:
    v1.add(TVec3.Create(2,3,4)); //memory-Leak
    v1.free;
end;


Der Anfang der Vektor-Klasse sieht in etwa so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
unit MatrixMath;

interface 

uses
    FastMM4;

type
    PVec3 = ^TVec3;
    TVec3 = class
        x, y, z: double;
                                                 
        constructor Create; overload;               
        constructor Create(v: TVec3); overload;
        constructor Create(v: double); overload
        constructor Create(x, y, z: double); overload;

        function copy: TVec3;
        
        function add(v: TVec3): TVec3; overload
        function add(v: double): TVec3; overload;
        function add(x, y, z: double): TVec3; overload;

        {...}


Das FastMM4 nutze ich nur für die Memory-Leak-Erkennung.


Grüße,
Symbroson


drstar - Mo 23.04.18 11:21

constructor mit overload halte ich für keine gute Idee. Um die Freigabe des Objekts kümmert sich destroy, deshalb kennzeichnet man auch beide mit inherited, um den ursprünglichen Konstruktor bzw. Destruktor aufzurufen. Um die Speicherverwaltung kümmert sich die Klasse dann allein, genau das ist ja der Sinn dahinter.
Um dann die Klasse (nicht das Objekt) wieder freizugeben, ruft man dann destroy auf, auch hier dann mit inherited, um nicht nur den ursprünglichen Destruktor aufzurufen, sondern auch alles "Selbstgemachte" mit zu entsorgen. Wichtig noch die Reihenfolge: Beim Konstruktor als erstes den Ursprungs-Konstruktor aufrufen und dann den eigenen abzuarbeiten, beim Destruktor erst alles Selbstgemachte beseitigen und dann den Ursprungs-Destruktor aufrufen. Dann sollte alles eigentlich kein Problem mit der Speicherverwaltung sein.


Symbroson - Mo 23.04.18 12:06

Ok, also ich muss mir das nachmal anschauen wie das zu benutzen ist, sodass das so funktioniert wie ich mir das vorstelle ^^

Danke schonmal für deine Antwort :)

Kannst du mir vllt mein kleines Beispiel so erweitern, dass sich die Klasse (irgendwann, möglichst bald, vllt nach Funktions-/Prozedurende) selbst zerstört?
Sonst bräuchte ich ja irgendwie nen Garbage-Collector...


Holgerx - Mo 23.04.18 12:12

Hmm..

Ich glaube, dass was Du haben willst geht mit 'normalen' Objecten nicht.

Hier werfe ich mal 'Interface' in den Raum, da diese eine Referenzzählung haben und freigegeben werden, wenn sie nicht mehr referenziert werden.


Delete - Mo 23.04.18 18:12

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Mo 23.04.18 18:36

Wow - vielen Dank! - das hilft mir sehr weiter. :zustimm: ich glaube, ich hätte sehr lange gebraucht, um das so hinzubekommen!

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var v1 : TVec3;
 
begin
    v1 := TVec3.Create(1,2,3);
    v1.add(4,5,6);        //okay
    v1.add(TVec3(7,8,9)); //memory-Leak
    v1.free;
end;

Das kann garnicht funktionieren, da du an jener Stelle den Klammerausdruck direkt castest. In C++ würdest du das auch nicht machen: (TVec3)(123).
Entschuldigung, es hätte heißen müssen  v1.add(TVec3.Create(2,3,4));

Zitat:
Und ich hab's dir schonmal gesagt, verwende einen try-finally Block, wenn du Objekte freigeben musst!
ja 'tschuldigung - ich hätte es später noch hinzugefügt ;)


noch eine Frage: Was genau bewirkt das property? Machen die Get bzw Set funktionen es nicht komplizierter? Oder wofür sind die sonst da?
[Edit:] ach, die sind bestimmt inherited-spezifisch. Das macht Sinn ^^

LG


Delete - Mo 23.04.18 19:10

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Mo 23.04.18 19:20

Achso dann ist das also soetwas wie bei Canvas.Pixels oder Scanline :idea:

Interessantes Konzept - zumindest flaggt jetzt MM4 nicht mehr :zustimm:

Es sieht auch so aus, als müsste ich für TVec3 und IVec3 jeweils weitere overloads erstellen...

[Edit:] Könnte Ich nicht anstatt der Vec3-class nur das Vec3-interface nehmen und dort alle Methoden hineinpacken? Das macht doch im Endeffekt keinen Unterschied, oder?


Delete - Mo 23.04.18 20:11

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Mo 23.04.18 21:38

Geht das irgendwie, dass ich eine allgemeingültige Free bzw Release-funktion sowohl für IVec3 als auch TVec3 habe? Sonst muss ich beide Objekte jeweils unterschiedlich freigeben (mit := nil bzw Free) - und das kann dann doch eher verwirrend sein...
Alles, was ich bisher versucht habe, hat nicht funktioniert. Ich habe einiges mit try..catch blöcken und _Release <= 0 bzw Free oder FreeAndNil() versucht, hat aber alles irgendwie nicht so ganz geklappt - meistens kam eine EInvalidPointer Exception oder ähnliches.

es sollte am Ende in etwa so aussehen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
var v1 : TVec3;
    v2 : IVec3;
 
begin
    v1 := TVec3.Create(2,3,5);
    v2 := Vec3(1,4,6);
    
    v2.x := v2.x + 9.4;
    writeln('1: (' + floattostr(v1.x) + '|' + floattostr(v1.y) + '|' + floattostr(v1.z) + ')');
    
    v1.add(v2.copy.mul(4));     
    writeln('2: (' + floattostr(v1.x) + '|' + floattostr(v1.y) + '|' + floattostr(v1.z) + ')');
    
    v1.Release;
    v2.Release;
    
    readln;
end.


Danke vielmals


Delete - Mo 23.04.18 23:25

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Di 24.04.18 06:11

copy gibt eine Kopie des Objektes/Interfaces zurück und mul() multipliziert die Werte der Vektoren bzw streckt/skaliert ihn mit einem Wert. Anders als scalar() für Skalarprodukte bzw cross() für Kreuzprodukte. Später werden mit mul() auch Multiplikationen mit einer 3×3 Matrix möglich.

Ich versuche eigentlich so etwas wie die glm Bibliothek von c++ zu schaffen - nur will ich alles auch mal selber gemacht haben anstatt immer nur fertige Bibliotheken zu nutzen - und wo das nun in Info schonmal dran is bietet sich ja dazu die Gelegenheit.


Delete - Fr 27.04.18 12:57

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 14:56

Das wäre wirklich nicht nötig gewesen - die Klasse selbst hab ich schon selber gebaut.
Ich habe ja auch schon selber mit glm in C++ OpenGL gearbeitet - ich bin mir dem Umpfang natürlich bewusst
Es kommen aber trotzdem noch Matritzen (3×3 und 4×4 für den Raum) dazu, sowie zumindest 2er Vektoren. Fang jetzt aber bloß nicht an das auch zu machen :lol:

Das copy hab ich hinzugefügt für den Fall, dass ich mit dem Wert einer Variable rechnen, aber nicht die Variable selbst verändern möchte - zB. für die ModelView Matrix für alle Vertices - die sollen ja nicht die Position des Vertex verändern.

Ich überlege auch noch, ob ich nicht vielleicht doch eher class functions/procedures verwende wie auch glm - Ich weiß ja nicht wie Delphi das dann handlet - ob je Funktion pro Kind ein extra Funktionszeiger angelegt werden muss, oder eben nicht. Bei Klassenfunktionen sollte das ja in jedem Fall nicht so sein.

Danke nochmal für deine Hilfe ^^

[Edit] Frage: warum legst du immer eine extra Variable an, anstatt direkt mit result zu arbeiten? Es gibt ja schon extra result... warum es nicht ausnutzen? (zumindest mache ich das meistens)


Delete - Fr 27.04.18 20:37

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 20:40

Ja, das ist mir auch aufgefallen. Dafür hab ich jetzt eine Release fnkt die für beide in den meisten Fällen funktioniert.

Ich hab mal meine aktuelle und fast finale Vec3 Klasse in den Anhang gepackt. Das mit dem IVec3 als Rückgabe versuche ich mal - das würde aber die Nutzung von IVec3 erzwingen, da TVec3 damit nicht kompatibel ist. (umgekehrt aber irgendwie schon...)

Bsp:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
program MatrixTest;
          
{$APPTYPE CONSOLE}

uses
    //FastMM4,
    SysUtils,
    //System, 
    //Dialogs, 
    UVec3 in 'glm/UVec3.pas';

var v1 : TVec3;
    v2 : IVec3;
begin
    try
        v1 := TVec3.Create(2,3,5);
        v2 := Vec3(1,4,6);
    
        v2.x := v2.x + 9.4;
        writeln(Format('2: |(%f|%f|%f)| = %f', [v1.x, v1.y, v1.z, v1.abs]));
    
        v2 := v1.add(v2.copy.mul(4)).copy;     
        writeln(Format('2: |(%f|%f|%f)| = %f', [v1.x, v1.y, v1.z, v1.abs]));

    finally
        TVec3.Release(v1);
        TVec3.Release(v2); //nicht benötigt, wenn in extra prozedur verwendet
    end
    
    readln;
end.


Delete - Fr 27.04.18 21:36

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 21:44

Aus irgendeinem Grund zeigt FastMM4 aber auch beim interface eine Warnung an, wenn ich es in dem Konsolenanwendungs-begin end. definiere. Deswegen das Release


Delete - Fr 27.04.18 21:55

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 22:00

Das hier erzeugt trotzdem eine Warnung:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
program MatrixTest;
          
{$APPTYPE CONSOLE}

uses FastMM4, SysUtils, {System,{} {Dialogs,{} UVec3 in 'glm/UVec3.pas';

var v1 : TVec3;
    v2 : IVec3;
begin        
    v1 := TVec3.Create(2,3,5);
    v2 := Vec3(1,4,6);
    
    try
        v2.x := v2.x + 9.4;
        writeln(Format('2: |(%f|%f|%f)| = %f', [v1.x, v1.y, v1.z, v1.abs]));
    
        //v1.add(v2.copy.mul(4).add(Vec3(5,3,-2))).copy;     
        writeln(Format('2: |(%f|%f|%f)| = %f', [v1.x, v1.y, v1.z, v1.abs]));

    finally
        TVec3.Release(v1);
        //TVec3.Release(v2); // not required when used in extra procedure
    end
    
    readln;
end.


Auch ganz ohne Befehle im try-Block kommt die Warnung.
Meine Theorie ist, dass entweder FastMM4 es nicht mitbekommt, also zu früh auswertet, oder dass Delphi zu spät merkt, dass v2 keine Referenzen mehr hat und nicht mehr freigibt.

Meistens verwendet man das ja eh nicht in einer Konsolenanwendung, sondern in ner Form oder zumindest in einer extra Funktion - dann hat sich das eh erledigt.
Im Zweifelsfall legt man sich einfach eine main-Prozedur an, in der man das ganze Zeug aus begin {...} end. hineinpackt.


Delete - Fr 27.04.18 22:10

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 22:12

Ja ist ja gut ^^ es ging mir wie gesagt nur darum, eine einheitliche Freigabe-Funktion zu haben
An der Warnung ändert es aber nichts.


Delete - Fr 27.04.18 22:17

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Fr 27.04.18 22:19

Von FastMM4 während des Beendens (vor Schließung der Konsole/des Ausgabefensters) dass ein TVec3 Objekt nicht freigegeben wurde, wenn ich v2 nicht am Ende manuell auf nil setze.


Delete - Sa 28.04.18 04:13

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Sa 28.04.18 08:37

user profile iconFrühlingsrolle hat folgendes geschrieben:
Was für eine Warnung?

fmm4err


user profile iconFrühlingsrolle hat folgendes geschrieben:
An welcher Stelle?

Es ist keine Delphi Fehlermeldung die eine bestimmte Zeile im Code bemängeln könnte, sondern eine Meldung, ass nach dem Beenden des Programms noch etwas nicht freigegeben wurde. Dass FastMM4 überhaupt den Objekttyp erkennt finde ich schon gut.


user profile iconFrühlingsrolle hat folgendes geschrieben:
Auf einen Selbstversuch mit FastMM4, deiner Klasse und deinem Beispiel (mit meinen Verbesserungsvorschlägen) komme ich zu dem Entschluss, dass FastMM4 nichts zu bemängeln hat

Normalerweise macht es das auch nicht. Du musst dafür die FastMM4Options.inc anpassen. Ich hab hier mal gesammelt was bei bei mir alles eingeschaltet ist:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
{$define UseCustomFixedSizeMoveRoutines}
{$define UseCustomVariableSizeMoveRoutines}
{$define ASMVersion}
{$define DetectMMOperationsAfterUninstall}
{$define FullDebugMode}
  {$define RawStackTraces}
  {$define LogErrorsToFile}
  {$define LogMemoryLeakDetailToFile}
  {$define AlwaysAllocateTopDown}
  {$define SuppressFreeMemErrorsInsideException}
{$define EnableMemoryLeakReporting}
  {$define HideExpectedLeaksRegisteredByPointer}
  {$define ForceMMX}
{$define EnableBackwardCompatibleMMSharing}

EnableMemoryLeakReporting ist auf jeden Fall das Entscheidende.
Für FullDebugMode musste ich noch die FastMM_FullDebugMode.dll in mein Projektordner kopieren - sonst hat die Delphi nicht gefunden.


user profile iconFrühlingsrolle hat folgendes geschrieben:
Außerdem ist deine Code-Formatierung nicht so gut:

Ich möchte mich nur ungern über Formatierung streiten. Ich weiß, dass mehrere Befehle pro Zeile nicht gern gesehen sind. Das ist eh erstmal nur eine komprimierte aber übersichtliche Version (nach dem Motto 'Alles auf einen Blick'), in der ich einfacher etwas hinzufügen kann und gleichzeitig überprüfen, ob die Befehle und rechnungen mit den anderen Overloads übereinstimmen. Falls ich es irgendwann doch mal veröffentlichen möchte, oder irgendwie weitergeben, muss ich sowiso erst noch Kommentieren, und dann wird auch diese gepackte Struktur hinfällig.

Da wäre deine Variante, jeweils auf die (x,y,z)-Funktion zuzugreifen eigentlich nicht verkehrt - nur muss ich dafür erst wieder eine weitere prozedur aufrufen - das kommt mir performancemäßig nicht ganz richtig vor - aber eigentlich ist es vernünftig. Ich glaube, das werde ich übernehmen


Delete - Sa 28.04.18 11:03

- Nachträglich durch die Entwickler-Ecke gelöscht -