Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Fragen zu OOP und Klassen (https://www.delphipraxis.net/103812-fragen-zu-oop-und-klassen.html)

Gonzo2 22. Nov 2007 06:50


Fragen zu OOP und Klassen
 
Ich will Klassen schreiben, nur verstehe ich kaum was davon. Ich hab mir einige Bücher, Tutorials und sonstige Onlinekurse durchgelesen, aber sie beantworten nicht alle meine Fragen. Es wird mir zu viel als gegeben angenommen oder wichtige Punkte mit einem Satz abgehandelt. Bei vielen Sachen will ich aber genauer wissen wieso es so funktioniert oder noch wichtiger, wieso es trotzdem funktioniert, obwohl ich es eigentlich anders machen sollte. Die ganzen Tutorials und Kurse werden anscheinend von Leuten geschrieben die die Materie beherrschen und sich deshalb bestimmte Fragen nicht mehr stellen. Deshalb behandeln sie sie auch nicht oder nicht so genau. So muß man bei Kursen einiges einfach akzeptieren ohne zumindest am Anfang zu verstehen wieso es so ist. Deshalb würde ich gerne hier einen Ergänzungskurs anfangen bei dem ich die Fragen stelle die für einen Anfänger interessant sind. Mir ist klar, daß es die Regel gibt pro Thread nur eine Frage zu stellen, aber ich hoffe man macht hier eine Ausnahme, so daß das ganze Thema zusammen bleiben kann. Also verschiedene Fragen aber zum Thema Klasse.

Ich hab viele Fragen, also fange ich mal an und nicht alles auf einmal.

Eine Klasse muß nicht kompliziert sein. Sie kann auch so aussehen:

Delphi-Quellcode:
TYPE
  TTest1 = class
  a: Byte;
end;
Das ist eine einfache Klasse und ich kann sie sofort nutzen ohne sie weiter auszubauen.

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  Test1: TTest1;
begin
  Test1 := TTest1.Create;
  Test1.a := 200;
  ShowMessage(IntToStr(Test1.a));
  Test1.Free;
end;
Da bei Klassen geerbt wird, hat meine Klasse auch einiges von seinem Vorfahr geerbt, darunter auch die Methoden Create und Free. Da ich keine Klasse als Vorfahr genannt habe, wird TObject automatisch angenommen. Somit erbt meine Klasse alle Methoden von TObject. Baue ich auf einem anderen Vorfahrtyp, muß ich den angeben.

Machen wir es jetzt komplizierter und geben a einen Anfangswert.

Delphi-Quellcode:
TYPE
  TTest1 = class(TObject)
  a: Byte;
  constructor Create;
end;

constructor TTest1.Create;
begin
  a := 1;
end;
Immer noch eine einfache Klasse, aber hier bekommt a in Create einen Anfangswert. Das Ganze funktioniert übrigens ohne inherited. Liest man sich aber Bücher und Tutorials durch, so steht an erster Stelle, egal wie einfach eine Klasse ist und ob die sich von TObject ableitet, in Create immer inherited. Bei mir nicht. Hab ich jetzt ein Sakrileg begangen? Ich stelle an dieser Stelle mal die Prinzipfrage ob inherited immer nötig ist. Laut etlicher Bücher und Kurse ja, denn das macht man nun mal so. Genauer wird an dieser Stelle selten eingegangen. Create, wie übrigens auch Destroy, von TObject ist nebenbei gesagt leer und ohne Inhalt. Wozu also hier Create mit inherited aufrufen? Wie man merkt möchte ich hier etwas über inherited diskutieren, da überall nur steht, daß es genommen werden soll, ohne genauer drauf einzugehen.

inherited ist dazu da um den Vorfahr aufzurufen. Baue ich auf einer Klasse auf und will nicht nur den Namen erben, sondern auch die Eigenschaften die sich im Vorfahr hinter der Funktion verbergen, dann muß ich den Vorfahr mit inherited aufrufen.

Wie ich allerdings in vielen Beispielen sehen konnte wird inherited genommen ohne groß zu überlegen. Einen Fehler macht man anscheinend nicht wenn man inherited in eine Klasse einbaut die direkt von TObject abstammt, aber wozu nehmen wenn nicht nötig. Das gleiche gilt wohl auch für Destroy. Eine andere Frage ist wozu inherited bei Create immer vorne und bei Destroy immer hinten genommen wird? Rufe ich bei Create mit inherited den Vorfahr der Klasse auf, ist die Sache klar. Zumindest wenn ich etwas nutze was mir der Vorfahr liefert. Nutze ich nichts vom Vorfahr am Anfang, sondern ergänze den nur, ist es doch egal wo inherited steht.

Beispiele wie es nicht üblich ist:

Delphi-Quellcode:
constructor TTest2.Create;
begin
  StringList := TStringList.Create;
  inherited;
end;
Das gleiche gilt wohl auch beim Destroy.

Delphi-Quellcode:
constructor TTest2.Destroy;
begin
  inherited;
  StringList.Free;
end;
Ich zähle das Ganze nur wegen dem Verständnis auf. Man kann inherited in jedes Create schreiben, muß es aber nicht wenn es nicht notwendig ist. Wenn man es aber nicht weiß ob es notwendig ist, dann ist es nicht falsch es einzusetzen. Schaden tut es nicht. Bei der Reihenfolge muß inherited bei Create auch nicht immer an erster Stelle stehen, wenn es nicht notwendig ist. Wenn man es aber nicht weiß ob es notwendig ist, dann ist es nicht falsch Create an erster Stelle zu setzen. Schaden tut auch das in der Regel nicht. Hab ich das richtig verstanden?

Ansonsten ist mir nicht ganz klar wieso Delphi das nicht automatisch erledigt. Warum ruft der constructor nicht automatisch den Vorfahr auf? Oder gibt es Situationen wo man den Vorfahr mit Absicht nicht aufruft? Oder ist das mit der Reihenfolge so wichtig, daß man es den Programmierer überläßt. Oder hat das mit constructor und destructor selbst nichts zu tun, denn bei jeder Methode und Eigenschaft muß aufs Neue überlegt werden ob und wo man inherited einsetzt?

Dann folgt bei mir die Frage woher ich wissen soll wo ich inherited wann einsetzten soll? Bei Create und Destroy ist das mehr oder weniger bekannt und deshalb geregelt. Wie aber ist das bei anderen Eigenschaften und Methoden? Wie sieht es bei Assign aus? Woher soll ich wissen wo es hingehört? Bei TStrings.Assign ist inherited hinten, bei TStringList.Destroy ist es sogar mitten drin. Bedeutet das letztendlich, daß wenn ich eine Klasse schreibe ich mich erst mit dem Vorfahr auseinendersetzen muß? Wie gesagt, destroy von TStringList ist mitten drin.

Das waren meine ersten Verständnisfragen. Kann man also abschließend sagen, daß wenn man Klassen schreibt man sich immer erst mit der Vorgängerklasse auseinandersetzen muß? Und lediglich bei einfachen Klassen die auf TObject aufbauen man sich die Arbeit sparen kann, da in der Regel dann nur Create und Destroy angepaßt werden und bei denen macht es keinen Unterschied ob man inherited nimmt oder nicht oder vorne oder hinten?

Luckie 22. Nov 2007 07:37

Re: Fragen zu OOP und Klassen
 
Du hast recht bei deinenen Beipsilen wäre es überflüssig. Man macht es aber meist trotzdem nur der Vollständigkeit halber. Bei CReate als erstes, weil man gegebenefalls Sachen im Konstruktor hat, die der Konstruktor der Vorfahrenklasse nicht wieder kaputt machen soll. Deswegen ruft Delphi den Konstruktor der Vorfahrenkalsse auch nicht automatisch auf, weil das nicht immer unbedingt gewünscht ist. Und bei Destroy als letztes, weil man erstmal seine abgeleitete Klasse aufräumt.

Guck dir mal mein Tutorial an: http//delphitutorials.michael-puff.de

Der_Unwissende 22. Nov 2007 07:42

Re: Fragen zu OOP und Klassen
 
Hi, erstmal vorweg finde ich Deine Idee gut, da ich damals (als ich die OOP verstehen wollte) auch kein wirklich gutes Tut. gefunden hatte. Ist allerdings auch ein paar Jahre her, vielleicht sind die ja schon besser geworden. Trotzdem ist es immer gut zu fragen und natürlich werden die Fragen auch gerne beantwortet.

Zitat:

Zitat von Gonzo2
Wie ich allerdings in vielen Beispielen sehen konnte wird inherited genommen ohne groß zu überlegen. Einen Fehler macht man anscheinend nicht wenn man inherited in eine Klasse einbaut die direkt von TObject abstammt, aber wozu nehmen wenn nicht nötig. Das gleiche gilt wohl auch für Destroy. Eine andere Frage ist wozu inherited bei Create immer vorne und bei Destroy immer hinten genommen wird?

Ganz so sinnlos wie es erscheint ist es nicht. Inherited ruft immer die gleichnamige Methode des Vorfahren auf. Dazu muss man kurz sagen, dass Klassen eben eine Art Tabelle besitzen, in der zu jeder virtuellen Methode ein Verweis existiert. Wird eine virtuelle Methode aufgerufen, so wird in der Tabelle nachgeschlagen, welche Methode verwendet werden soll. Anders als bei statischen Methoden können virtuelle auch durch einen Nachfahren überschrieben (direktive override) werden. Damit ändert man den Eintrag in dieser Tabelle, der verwendet wird. Ebenso kann man Methoden durch eine gleich benannte Methode überdecken. Hier wird in der Tabelle dann geschaut, von welcher "Art" / welchem "Typ" die Klasse ist. Das ist nötig, da jede erbende Klasse auch wie einer ihrer Vorfahren verwendet werden kann (z.B. kann eine TStringList als TStrings oder als TObject verwendet werden). Für jede der möglichen Klassen (also die eigentliche Klasse selbst, sowie alle Vorfahren) existiert dann ein Eintrag in dieser Tabelle für jede Methode.

Hierin liegt auch díe eigentliche Begründung für die Position von inherited. Das Create eines TObject hat die grundlegenden Dinge zu erledigen, es muss Speicher alloziert, diese Tabelle erzeugt und ein Verweis zurückgegeben werden. Vieles von dem Code bleibt aber verborgen, da es sehr spezifisch ist. Deshalb wirkt das Create nur leer, tatsächlich wird auch dort (natürlich) Code ausgeführt. Es macht natürlich für jede Klasse Sinn, dass man erst den nötigen Speicher alloziert usw. und erst dann weitermacht. Dass es auch ohne klappt hat nicht viel zu sagen, das kann auch von Fall zu Fall mal ordentlich nach hinten losgehen. Da gibt es dann eine Access Violation.
Ebenso ist das Destroy von TObject für das Aufräumen des Speichers zuständig. Dabei wird eben das eigentliche Objekt (also z.B. auch die genannte Tabelle) aus dem Speicher entfernt. Entsprechend kannst Du nicht mehr auf das Objekt zugreifen, wenn das inherited Destroy aufgerufen wurde. Die anderen Arbeiten die im (eigenen) Destruktor stattfinden sind i.d.R. das Aufräumen von Datentypen, auf die nur ein Verweis gespeichert wurde (dyn. Arrays, Klassen, Zeiger). Dort würde das inherited sonst nur den Speicher für den Verweis löschen, die eigentlichen "Objekte" (hier nicht im OO-Sinne gemeint) würden einfach unerreichbar im Speicher verbleiben.

So, muss leider gerade los, werde aber bei Gelegenheit mit noch den Rest anschauen und antworten.

Gruß Der Unwissende

Gonzo2 22. Nov 2007 09:22

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von Luckie
Guck dir mal mein Tutorial an: http//delphitutorials.michael-puff.de

Hab ich schon, ist ein Teil meines Grundwissens, aber es behandelt einige Punkte leider sehr kurz.

Zitat:

Zitat von Der_Unwissende
Hierin liegt auch díe eigentliche Begründung für die Position von inherited. Das Create eines TObject hat die grundlegenden Dinge zu erledigen, es muss Speicher alloziert, diese Tabelle erzeugt und ein Verweis zurückgegeben werden. Vieles von dem Code bleibt aber verborgen, da es sehr spezifisch ist. Deshalb wirkt das Create nur leer, tatsächlich wird auch dort (natürlich) Code ausgeführt. Es macht natürlich für jede Klasse Sinn, dass man erst den nötigen Speicher alloziert usw. und erst dann weitermacht. Dass es auch ohne klappt hat nicht viel zu sagen, das kann auch von Fall zu Fall mal ordentlich nach hinten losgehen. Da gibt es dann eine Access Violation.

Ein interessanter Ansatz, der erstmal etwas nachdenklich macht. Soll man auch eine leere Create aufrufen, weil die Create von TObject den Speicher reserviert? Damit hast du mich im ersten Moment wirklich etwas verwirrt und nachdenklich gemacht. Aber dann habe ich mich gefragt, ist es nicht die Aufgabe von constructor den Speicher zu reservieren? Es stimmt schon, der Create von TObject reserviert Speicher, aber muß man ihn deswegen aufrufen? Ich weiß nicht inwieweit das für den Vorgänger wichtig ist. Muß man es machen oder erledigt mein constructor alles, auch für den Vorfahr? Somit wäre das Aufrufen des Vorfahrs nicht nötig.

Du hast mit den virtuellen und statischen Methoden etwas dem Thema vorweg gegriffen. Auf das Thema wollte ich etwas später eingehen. Auch da habe ich Fragen, aber alles der Reihe nach, sonst kommen wir durcheinander. Allerdings kann man insoweit schon darüber diskutieren ob Create ohne overload das Create des Vorgängers ersetzt oder überschreibt. Ich bin zwar noch nicht soweit, aber dann würde sich wirklich die Frage stellen ob man immer inherited aufrufen sollte.

Luckie 22. Nov 2007 09:26

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von Gonzo2
Zitat:

Zitat von Luckie
Guck dir mal mein Tutorial an: http//delphitutorials.michael-puff.de

Hab ich schon, ist ein Teil meines Grundwissens, aber es behandelt einige Punkte leider sehr kurz.

Welche? Das könnte man ja ändern.

xaromz 22. Nov 2007 09:29

Re: Fragen zu OOP und Klassen
 
Hallo,

Zitat:

Zitat von Der_Unwissende
Hierin liegt auch díe eigentliche Begründung für die Position von inherited. Das Create eines TObject hat die grundlegenden Dinge zu erledigen, es muss Speicher alloziert, diese Tabelle erzeugt und ein Verweis zurückgegeben werden. Vieles von dem Code bleibt aber verborgen, da es sehr spezifisch ist. Deshalb wirkt das Create nur leer, tatsächlich wird auch dort (natürlich) Code ausgeführt.

Das stimmt nicht! Der Speicher (und die anderern genannten Dinge) wird (durch Compilermagic) beim Aufrufen des Konstruktors reserviert, und zwar bevor der Konstruktor ausgeführt wird. Durch inherited aufgerufene Konstruktoren und Destruktoren werden (wieder per Compilermagic) wie ganz normaler Methoden aufgerufen.

Der eigentliche Grund für das inherited auch bei TObject ist, dass Codegear ja jederzeit auf die Idee kommen können, Konstruktor oder Destruktor von TObject zu verwenden. Wenn dann in den Ableitungen das inherited fehlt...

Gruß
xaromz

sirius 22. Nov 2007 09:43

Re: Fragen zu OOP und Klassen
 
Schau dir mal das an. Da steht es sehr gut beschrieben.
Der "erste Aufruf" des Constructors führt also die Funktion ClassCreate aus, welche dem Objekt Leben einhaucht/ Speicher reserviert. Mit inherited rufst du nur den Vorfahr als ganz normale Methode auf. Wann und ob du das machst ist vollkommen dir überlassen. Und dabei ist es egal ob es sich um einen Constrcutor, einen Destructor oder eine Methode handelt.

Warum wird es nun standardmäßig so gehandhabt, wie du beschreibst?

a) Bei einer direkten Ableitung von TObject ist es derzeit nicht notwendig, da der Constrcutor von TObject ja leer ist. Aber wer sagt, dass es in Zukunft so bleiben wird. Falls es CG mal einfällt und in der Version 2008 etwas wichtiges im Constructor von TObject steht, dann brauchst du nicht deine ganzen constructors ändern. Ausserdem gibts seit :gruebel: 2006 Class Helper. Damit kannst du den Constructor in einem Projekt im Nachinein ändern.
Aber es bleibt trotzdem deine Entscheidung ob du Änderungen in TObject mitmachen willst.

b) Die Position des inherited erwies sich bisher so als am günstigsten. Wenn es irgendwie wichtig wist, die Reihenfolge zu beachten, dann meist in der beschriebenen Reihenfolge. Wenn es nicht wichtig war, dann kann man es auch so machen. Stört ja niemanden.

Gonzo2 22. Nov 2007 10:07

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von xaromz
Der eigentliche Grund für das inherited auch bei TObject ist, dass Codegear ja jederzeit auf die Idee kommen können, Konstruktor oder Destruktor von TObject zu verwenden. Wenn dann in den Ableitungen das inherited fehlt...

Das ist natürlich ein Argument, auch wenn ich nicht unbedingt glaube, daß es so kommen wird. Klassen verbessert man oder erweitert sie, aber man ändert nicht die Funktionsweise. Eine Änderung an dieser Stelle würde alle Klassen betreffen.

Tyrael Y. 22. Nov 2007 14:13

Re: Fragen zu OOP und Klassen
 
Letzendlich, wie schon von dir erkannt und mehrfach gesagt, ist das inherited im Moment bei einer Klasse, die von TObject abgeleitet ist nicht notwendig.

Du sagst jetzt du kannst dir nicht vorstellen, daß man das Create von TObject verändern wird. Völlig ok deine Meinung. Es bleibt aber immer noch eine Meinung bzw. ein Glaube, es gibt keine Garantie dafür.

Man sollte stets defensiv programmieren und genau in diesem Moment ist das einfügen des inherited ein defensives Verhalten, was dich auf die sichere Seite bringt.


Mal ein kurzes anderes Beispiel zum defensiven Programmieren:


Delphi-Quellcode:
procedure TuWas();
var LMyObj: TEinTyp:
begin
  LMyObj := TEinTyp.Create;
  LMyObj.MachMalDeineAufgabe();
  LMyObj.Free;
end;

das ganze nochmal defensiv:
Delphi-Quellcode:
procedure TuWas();
var LMyObj: TEinTyp:
begin
  LMyObj := TEinTyp.Create;
  try
    LMyObj.MachMalDeineAufgabe();
  finally
    LMyObj.Free;
  end;
end;
Im ersten Fall könnte dein Objekt freigegeben werden, im zweiten Fall wird es garantiert freigegeben.

Gonzo2 23. Nov 2007 08:21

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von Tyrael Y.
Im ersten Fall könnte dein Objekt freigegeben werden, im zweiten Fall wird es garantiert freigegeben.

Ja. Das ist kein unwichtiger Punkt, aber an dieser Stelle nicht so wichtig. Es ist schon klar, daß Objekte in einen Schutzblock gehören, aber bei Verständnisfragen ist alles was von dem Notwendigen ablenkt zuviel. Deshalb habe ich hier bei den Beispielen mit voller Absicht auf den Schutzblock verzichtet. Sonst werden bei mir Objekte eingepackt.

Gonzo2 23. Nov 2007 08:35

Re: Fragen zu OOP und Klassen
 
Vielen Dank an alle die mit mir meine Verständnisfragen durchgenommen haben. inherited ist mir jetzt klarer. Abschließend kann man sagen, daß inherited einfach nur den Vorfahr aufruft. Was sich so einfach anhört ist Anfängern nicht sofort klar. Erst wenn man die Konsequenzen sieht wird es klarer. In einer Klasse die auf TObject aufbaut kann man in Create auf inherited verzichten, da im Create von TObject, also dessen Vorfahr, nichts passiert. Beim Arbeiten mit anderen Vorfahrklassen könnte dagegen der fehlende Aufruf von inherited böse Folgen haben, da dann der Vorfahr nicht aufgerufen wird und später einige geerbte Eigenschaften oder Methoden ins leere laufen.

Hier ein Beispiel das ich mir zum Verständnis erstellt habe. Ich hab mir eine eigene IniFile Klasse geschrieben bei der nur Create ersetzt wurde. Rufe ich kein inherited auf, hat das Konsequenzen und im Beispiel tritt bei WriteString ein Fehler auf.

Delphi-Quellcode:
type
  TMeineIniFile = class(TIniFile)
  public
    constructor Create(const FileName: string);
  end;

constructor TMeineIniFile.Create(const FileName: string);
begin
  //inherited Create(FileName); //mal mit mal ohne
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  IniFile: TMeineIniFile;
begin
  IniFile := TMeineIniFile.Create('c:\temp\tmp.ini');
  try
    IniFile.WriteString('a', 'b', 'Gefunden');
    ShowMessage(IniFile.ReadString('a', 'b', 'Nicht gefunden'));
  finally
    IniFile.Free;
  end;
end;
Zwar sind einige meiner Fragen zu inherited noch nicht beantwortet, aber an dieser Stelle genügt es. Ich hacke später nochmal nach.

Gonzo2 23. Nov 2007 08:52

Re: Fragen zu OOP und Klassen
 
Weiter in Verständnisfragen zu Klassen: private, public, published und protected

Neue Fragen, neuer Thread. Hier geht es zu Fragen zu OOP und Klassen: published, protected, ...

sirius 23. Nov 2007 08:57

Re: Fragen zu OOP und Klassen
 
halt dich bei inherited nicht zu sehr am Constructor auf. Inherited leitet einen ganz normalen Methodenaufruf ein. eben nur mit dem Unterschied, dass man die Vorfahrklasse meint. Man kann inherited in jeder Methode nutzen.

Der Constructor ist eben eine ganz normale Methode, die nach ein paar Ergänzungen etwa so aussieht:
Delphi-Quellcode:
procedure myconstructor(param1,param2:integer; und hier die restlichen Parameter);
//param1 und param2 sind "versteckte Parameter" welche immer übergeben werden, wenn statt procedure constructor da steht
begin
  //so und jetzt der Code, der eigentlich vor begin steht
  if param2=1 then
  begin
    //param2=1, wenn man TMyClass.create aufruft
    self:=System.ClassCreate(param1); //param1 ist ein Pointer auf die RTTI der Klasse
    //System.ClassCreate reserviert den Speicher für alle Felder (wird wie ein Record behandelt)
    //und ruft noch self.afterconstruction auf, was meist leer ist
  end
  else
    //param2=0 wenn man myClass.Create oder eben self.Create oder inherited create aufruft
    self:=param1; //Jede Methode hat einen ersten versteckten Parameter, und der ist immer self
 
  //und erst hier kommt dein normaler Code aus der Methode/ dem Constructor
end;
Eigentlich könnte der Programmierer, dass ja auch selber erldigen.
Wenn er eine neue Instanz einer Klasse initialisieren will, kann er ja selber ClassCreate aufrufen. In Pascal/Delphi ist es halt einfacher: Da man ja meist eh irgendwelche wichtigen Initialisierungen des Objektes (z.B. IniFile öffnen) machen muss, hat man irgendeine Init-Methode. Also man würde meist ClassCreate aufrufen und dann die Init-Methode. Dies wurde einfach zusammengelegt und nennt sich jetzt Constructor. Und weil halt in den Constructoren meist wichtige Sachen erledigt werden, ist es günstig den Constructor des Vorfahren auch mit aufzurufen (ist ja nur zur Initialisierung).
Und wie gesagt: Bei anderen (virtuellen) Methode kann das auch sinnvoll sein. Es sei denn man überschriebt sie, weil man eben nicht will, was in der Vorfahrklasse gemacht wurde.


Edit: neue Frage, neuer Thread. Sonst kommen wir hier ganz durcheinander :zwinker:

Gonzo2 23. Nov 2007 09:20

Re: Fragen zu OOP und Klassen
 
Danke Dir, war noch eine gute Ergänzung. Du hast Recht, inherited wird nicht nur in Create angewendet. Jedes mal wenn man eine Methode ergänzt oder ändert, ruft man die Vorfahrmethode auf. Nur kommt bei den Beispielen am häufigsten inherited in Create und Destroy vor.

Zitat:

Zitat von sirius
Edit: neue Frage, neuer Thread. Sonst kommen wir hier ganz durcheinander :zwinker:

Ich hoffe nicht. Das Thema ist Verständnisfragen zu OOP und Klassen. Am liebsten wäre mir alles in einem Thread. Sollte das nicht funktionieren, kann man bei weiteren Verständnisfragen neue Threads öffnen, auch wenn sie dann das Thema verteilen.


Edit: vielleicht hast du Recht. Vielleicht kommt man doch zu sehr durcheinander. Ich hab eine neues Thema erstellt und die Themen gesenseiteig verlinkt. Somit will ich auch für Andere die das Thema interessiert eine Art Roten Faden durch das Thema ziehen.

Tyrael Y. 23. Nov 2007 09:25

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von Gonzo2
Zitat:

Zitat von Tyrael Y.
Im ersten Fall könnte dein Objekt freigegeben werden, im zweiten Fall wird es garantiert freigegeben.

Ja. Das ist kein unwichtiger Punkt, aber an dieser Stelle nicht so wichtig. Es ist schon klar, daß Objekte in einen Schutzblock gehören, aber bei Verständnisfragen ist alles was von dem Notwendigen ablenkt zuviel. Deshalb habe ich hier bei den Beispielen mit voller Absicht auf den Schutzblock verzichtet. Sonst werden bei mir Objekte eingepackt.

Es ging mir NICHT um den Schutzblock, sondern um das defensive Programmieren, was ich anhand eines Schutzblockes zeigen wollte. ;)

sirius 23. Nov 2007 09:30

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von Gonzo2
Nur kommt bei den Beispielen am häufigsten inherited in Create und Destroy vor.

Weil genau die wichtigsten Initialisierungen gemacht werden, die notwendig sind, damit die Klasse überhaupt benutzt werden kann (siehe dein Inifile-Beispiel)

Zitat:

Zitat von Gonzo2
Ich hoffe nicht. Das Thema ist Verständnisfragen zu OOP und Klassen. Am liebsten wäre mir alles in einem Thread. Sollte das nicht funktionieren, kann man bei weiteren Verständnisfragen neue Threads öffnen, auch wenn sie dann das Thema verteilen.

Das Thema sollte eigentlich auch eher exkater formluiert werden / an die "erste Frage" angepasst werden.

du musst im Auge behalten, dass dieser Thread nicht nur für dich ist, sondern in Zukunft auch über Suchmaschinen oder die interne Suche von anderen aufgerufen wird. Und für die wäre so ein Thread mit mehreren Themen keine Hilfe

Gonzo2 23. Nov 2007 09:49

Re: Fragen zu OOP und Klassen
 
Hast Recht, ich hab es geändert und neues Thema erstellt. Ich hab die beiden Themen gegenseitig verlinkt, so daß jedes Thema zwar für sich ist, aber jeder Interessierte auch weitere Fragen lesen kann. Es werden noch paar Threads werden.

An einen Moderator der das liest. Bitte Anfangstitel in Fragen zu OOP und Klassen: inherited ändern.

Gonzo2 23. Nov 2007 15:35

Re: Fragen zu OOP und Klassen
 
Vielleicht noch eine Frage zu Create. Deswegen lohnt nicht ein extra Thread und wir haben hier sowieso viel über Create diskutiert.

In der Regel wird nur ein Create benötigt, man kann aber mehrere erstellen.

Delphi-Quellcode:
type
  TMyClass = class
  private
    fCreated: TDateTime;
  public
    constructor CreateA();
    constructor CreateB(Int: Integer);
    function getCreated: TDateTime;
  end;

constructor TMyClass.CreateA();
begin
  fCreated := now;
end;

constructor TMyClass.CreateB(Int: Integer);
begin
  fCreated := now + Int;
end;

function TMyClass.getCreated: TDateTime;
begin
  result := fCreated;
end;
Der Konstruktor reserviert Speicher, füllt den mit Nullen usw. Rufe ich also CreateA auf, ist alles klar, es wird ein Objekt erstellt. Was passiert aber wenn ich hinterher CreateB aufrufe?

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.CreateA;
  try
    ShowMessage(DateToStr(MyClass.getCreated));
    MyClass := TMyClass.CreateB(10);
    ShowMessage(DateToStr(MyClass.getCreated));
  except
    MyClass.Free;
  end;
end;
Eine Fehlermeldung gibt es nicht, warum auch. Aber was passiert hier? Wird ein zweites Objekt erstelt? Oder bleibt es das Objekt, nur bekommt das Objekt einen neuen Anfangszustand? Wird alles wieder genullt? Oder wird einfach nur fCreated neu gesetzt? Was passiert wenn der Kreator das zweite mal aufgerufen wird?

sirius 23. Nov 2007 16:02

Re: Fragen zu OOP und Klassen
 
In deinem Beispiel ist es ein Problem.
MyClass ist ja nur ein Zeiger auf eine Instanz deiner Klasse. Letztenendes ist es nur ein Record, welches als erstes Element einen Zeiger auf die Klasse (also die RTTI) hat.

Ein Objekt besteht ja immer aus dem Bauplan (die RTTI) und den Methoden der Klasse. Die gibts in jedem Programm nur einmal. Und dann gibts eben noch die Instanz, was im Prinzip der oben beschriebene Record ist.
Rufst du jetzt zweimal einen Constructor mit TmyClass.Create auf (also so dass innerhalb des constructors ClassCreate aufgerufen wird) bekommst du jedes Mal einen Zeiger auf das neue Record zurück. Dadurch dass die zwei Constructoren unterschiedlich sind, sind die Records womöglich unterschiedlich initialisiert (je nachdem, was in dem jeweiligen Constructor drin steht).

In deinem Besipiel überschreibst du den Zeiger auf den ersten Record und bekommst dadurch ein Speicherloch (du kannst den Speicher nicht mehrfreigeben, da du nicht mehr weist, wo er ist). Und bei einem Speicherloch meckert kein compiler und auch kein Processor und auch kein Windows. Ausser es ist dann irgendwann nach vielen Aufrufen kein freier Speicher mehr da.

Speichertechnisch gleiches erriechst du mit folgendem Code:
Delphi-Quellcode:
type PmyInstance=^TmyInstance;
     TmyInstance=record
       RTTI:pointer; // der besagte erste Zeiger auf die RTTI
       fCreated:Tdatetime;
end;
...

procedure inita(Instance:PmyInstance);
begin
  Instance.fcreated:=now;
end;
procedure initb(Instance:PmyInstance;int:integer);
begin
  Instance.fcreated:=now+int;
end;



var myInstance:PmyInstance;
begin
  new(myInstance);
  inita(myInstance);

  new(myInstance);
  initb(myInstance);

  dispose(myInstance);
end;
Ist fast dasselbe wie OOP, nur dass hier keine Veerbung, Polymorphie, etc. möglich ist. Und daran siehst du auch, warum man new und init zu einem Constructor zusammengefasst hat. Und du siehst auch, dass ich den Speicher vom ersten "new" nicht mehr freigeben kann.

Was man aber an deinem Code machen kann, ist den zweiten Constructor als normale Methode aufzurufen (param2=0):
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.CreateA;
  try
    ShowMessage(DateToStr(MyClass.getCreated));
    MyClass.CreateB(10); //hier ist der Unterschied, CreateB wird als normale Methode aufgerufen (ohne ClassCreate)
    ShowMessage(DateToStr(MyClass.getCreated));
  except
    MyClass.Free;
  end;
end;

Gonzo2 23. Nov 2007 18:36

Re: Fragen zu OOP und Klassen
 
Ich frage deshalb, weil ich irgendwann irgendwo gelesen habe, daß immer nur ein Konstrukt ausgeführt wird egal wie viele Konstrukte eine Klasse hat. Ich weiß aber leider nicht mehr wo das stand. Es stand da in etwa, daß nur der erste Create den Speicher reserviert. Die anderen sollen dann nur noch alles zwischen begin und end ausführen, aber das Objekt nicht mehr initialisieren. Ich such das mal.

sirius 23. Nov 2007 20:40

Re: Fragen zu OOP und Klassen
 
Ja, das ist auch richtig, habe ich schon in mehreren Beiträgen geschrieben. aber schau dir mal genau den Unterschied an zwischen deinem und meinem Code!

Gonzo2 23. Nov 2007 22:04

Re: Fragen zu OOP und Klassen
 
Zitat:

Zitat von sirius
Ja, das ist auch richtig, habe ich schon in mehreren Beiträgen geschrieben. aber schau dir mal genau den Unterschied an zwischen deinem und meinem Code!

Ach das meinst Du. Da habe ich mich vertann, sehe's gerade. Anscheinend habe ich die Zeile instinktiv geschrieben, weil Create drinn vorkamm. Gemeint war es so wie bei dir.


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:18 Uhr.

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