Programmierkurs: Delphi: Pascal: Konstruktoren und Destruktoren
Konstruktoren und Destruktoren
BearbeitenAllgemeines
BearbeitenWie bereits im Kapitel über Klassen beschrieben, muss der Speicher für die Datenstruktur einer Klasse angefordert werden, bevor man mit ihr arbeiten kann. Da eine Klasse mehr als nur eine Datenansammlung ist - nämlich ein sich selbst verwaltendes Objekt - müssen gegebenenfalls weitere Initialisierungen durchgeführt werden. Genauso kann nach der Arbeit mit einer Klasse noch Speicher belegt sein, der von Delphis Speicherverwaltung nicht erfasst wird.
Für zusätzliche anfängliche Initialisierungen stehen die Konstruktoren („Errichter“) zur Verfügung, wogegen die Destruktoren („Zerstörer“) die abschließende Aufräumarbeit übernehmen. Diese sind spezielle Methoden einer Klasse, die nur zu diesem Zweck existieren.
Konstruktoren
BearbeitenFür die Arbeit mit einfachen Klassen, die keine weitere Initialisierung benötigen, braucht kein spezieller Konstruktor verwendet zu werden. In Delphi stammt jede Klasse automatisch von der Basisklasse TObject ab und erbt von dieser den Konstruktor Create. Folgender Aufruf ist daher gültig, obwohl keine Methode Create definiert wurde:
type TErsteKlasse = class end; var Versuch: TErsteKlasse; begin Versuch := TErsteKlasse.Create; end.
Wie zu sehen ist, erfolgt der Aufruf des Konstruktors über den Klassentyp. Eine Anweisung wie Variable.Create
führt zu einer Fehlermeldung.
Das „einfache“ Create erzeugt ein Objekt des Typs TErsteKlasse und gibt einen Zeiger darauf zurück.
Verwendet man Klassen, welche die Anfangswerte ihrer Felder einstellen oder z.B. weiteren Speicher anfordern müssen, so verdeckt man die geerbte Methode mit seiner eigenen:
type TErsteKlasse = class constructor Create; end; constructor TErsteKlasse.Create; begin inherited; { Eigene Anweisungen } end;
Das Schlüsselwort inherited ruft dabei die verdeckte Methode TObject.Create auf und sorgt dafür, dass alle notwendigen Initialisierungen durchgeführt werden. Daher muss dieses Schlüsselwort immer zu Beginn eines Konstruktors stehen.
Solange ein gleichnamiger Vorfahre aufgerufen wird, reicht das Schlüsselwort inherited, ohne weitere Angaben. Unterscheidet sich der Bezeichner vom Vorfahr im Namen oder Parametern, sollte nach inherited der Vorfahre genannt werden. Besteht der Konstruktor also nur aus dem Bezeichner Create, reicht inherited, bekommt er weitere Parameter, z. B. Create(Index: Word), kann der Vorfahre nicht mehr erkannt werden. In solchen Fällen sollte der Vorfahre genannt werden: inherited Create.
Beispiel: Sie wollen eine dynamische Adressliste schreiben, bei der der Name Ihrer Freundin immer als erster Eintrag erscheint.
type TAdresse = record Vorname, Nachname: string; Anschrift: string; TelNr: string; end; TAdressListe = class FListe: array of TAdresse; constructor Create; end;
In diesem Falle erstellen Sie den Konstruktor wie folgt:
constructor TAdressListe.Create; begin inherited; SetLength(FListe, 1); // Speicher für 1. Eintrag anfordern FListe[0].Vorname := 'Barbie'; FListe[0].Nachname := 'Löckchen'; FListe[0].Anschrift := 'Puppenstube 1, Kinderzimmer'; FListe[0].TelNr := '0800-BARBIE'; end;
Wenn Sie jetzt eine Variable mit diesem Konstruktor erstellen, enthält diese automatisch immer den Namen der Freundin:
var Adressen: TAdressListe; begin Adressen := TAdressListe.Create; with Adressen.FListe[0] do {Ausgabe: "Barbie Löckchen wohnt in Puppenstube 1, Kinderzimmer und ist erreichbar unter 0800-BARBIE."} Writeln(Vorname+' '+Nachname+' wohnt in '+Anschrift+' und ist erreichbar unter '+TelNr+'.') end.
Destruktoren
BearbeitenDestruktoren dienen, wie schon oben beschrieben dazu, den von einer Klasse verwendeten Speicher wieder freizugeben. Auch hierfür stellt die Basisklasse TObjekt bereits den Destruktor Destroy bereit. Dieser gibt grundsätzlich zwar den Speicher einer Klasse wieder frei, aber nur den des Grundgerüstes. Wenn eine Klasse zusätzlichen Speicher anfordert, z.B. weil sie mit Zeigern arbeitet, wird nur der Speicher für den Zeiger freigegeben, nicht aber der Speicherplatz, den die Daten belegen.
Eine Klasse sollte daher immer dafür sorgen, dass keine Datenreste im Speicher zurückbleiben. Dies erfolgt durch Überschreiben des Destruktors TObject.Destroy mit einem eigenen:
type TZweiteKlasse = class destructor Destroy; override; end; destructor TZweiteKlasse.Destroy; begin { Anweisungen } inherited; end; var Versuch: TZweiteKlasse; begin Versuch := TZweiteKlasse.Create; Versuch.Destroy; end.
Eine eigene Implementation von Destroy muss immer mit dem Schlüsselword override versehen werden, da Destroy eine virtuelle Methode ist (mehr dazu unter Virtuelle Methoden). Auch hier kommt wieder das Schlüsselwort inherited zum Einsatz, welches den verdeckten Destruktor TObject.Destroy aufruft. Da dieser Speicher frei gibt, muss inherited innerhalb eines Destruktors immer zuletzt aufgerufen werden.
Wenn wir unser Beispiel der Adressliste erweitern und auf das Wesentliche reduzieren, ergibt sich folgendes Programm:
type TAdresse = record Vorname, Nachname: string; Anschrift: string; TelNr: string; end; TAdressListe = class FListe: array of TAdresse; destructor Destroy; override; end; destructor TAdressListe.Destroy; begin SetLength(FListe, 0); // Speicher der dynamischen Liste freigeben inherited; // Objekt auflösen end; var Adressen: TAdressListe; begin Adressen := TAdressListe.Create; { Anweisungen } Adressen.Destroy; end.
Die Methode Free
BearbeitenFree dient ebenso wie Destroy dazu, den Speicher einer Klasse freizugeben. Dabei funktioniert Free jedoch etwas anders.
Destroy gibt ausschließlich den Speicher des Objektes frei, von dem aus es aufgerufen wurde. Bei allen Objekten, die von TComponent abstammen, gibt Free zusätzlich den Speicher aller Unterkomponenten frei. TComponent wird in der Programmierung grafischer Oberflächen verwendet.
Weiterhin können Sie Free auch bei Objekten anwenden, deren Wert nil ist, die also noch nicht mittels Create erstellt wurden. Destroy löst in diesem Falle eine Exception aus. Hierbei ist jedoch zu beachten, dass nur globalen Objektvariablen und Klassenfeldern automatisch nil zugewiesen wird. Lokale Variablen von Routinen bzw. Methoden enthalten anfangs meist Datenmüll, also nicht nil. Free versucht daher einen ungültigen Speicherbereich freizugeben, was zu einer Exception führt. Objektvariablen, die nicht mit Sicherheit initialisiert werden, sollten Sie daher am Anfang einer Routine oder Methode den Wert nil manuell zuweisen.
Aufgrund dieser Vorteile sollten Sie immer Free bevorzugen, um ein Objekt aufzulösen.
Richtige Verwendung von Free und Destroy
BearbeitenWenn Sie in einer Nachfahren-Klasse die Funktion des Destruktors erweitern wollen, überschreiben Sie immer Destroy direkt und rufen dort die benötigten Anweisungen auf. Zum tatsächlichen Freigeben eines Objektes verwenden Sie jedoch Free! Diese Methode ruft automatisch den Destruktor auf.
type TMyClass = class public destructor Destroy; override; end; destructor TMyClass.Destroy; begin { eigene Freigaben } inherited; end; var MyClass: TMyClass; begin MyClass := TMyClass.Create; MyClass.Free; end;
Überladen
BearbeitenDas Überladen von Konstruktoren und Destruktoren ist genauso möglich wie bei anderen Methoden auch. Für das Beispiel unserer Adressliste könnten zum Beispiel zwei Konstruktoren bestehen: einer, der eine leere Liste erzeugt, und einer, der eine Liste aus einer Datei lädt. Genauso könnte ein Destruktor die Liste einfach verwerfen, während ein anderer die Liste vorher in einer Datei speichert.
Sie können die Konstruktoren und Destruktoren jeweils gegenseitig aufrufen. Sie sollten hierbei jedoch darauf achten, dass nur genau einer das Schlüsselwort inherited aufruft.
type TAdressListe = class constructor Create; overload; constructor Create(Dateiname: string); overload; procedure DateiLaden(Dateiname: string); end; procedure TAdressListe.DateiLaden(Dateiname: string); begin {...} end; constructor TAdressListe.Create; begin inherited; {...} end; constructor TAdressListe.Create(Dateiname: string); begin Create; // ruft TAdressListe.Create auf Dateiladen(Dateiname); end;
Weiteres zum Thema Überladen in den Kapiteln „Prozeduren und Funktionen“ sowie „Klassen“.
Pascal: Klassen | Inhaltsverzeichnis | Pascal: Eigenschaften |