Home » Object Pascal » Objekte erzeugen und freigeben

Objekte erzeugen und freigeben

Objekte erzeugen: Der Konstruktor

Instanzen einer Klasse entstehen erst dadurch, dass sie ausdrücklich erzeugt werden. Dies geschieht mit dem Konstruktor Create. „Konstruktor“ ist ein Fremdwort und bezeichnet jemanden, der etwas konstruiert, also erzeugt. Man könnte create auch eine Methode nennen; es gibt jedoch einen großen Unterschied: Methoden können nur aufgerufen werden, wenn die Instanz bereits existiert. Der Konstruktor dagegen erzeugt die Instanz aus dem Nichts.
Der Konstruktor muss nicht von uns programmiert werden, da alle Klassen, die wir erfinden, automatisch den „Vorfahr“ TObject haben und damit alle grundlegenden Dinge von diesem erben. Weitere Informationen über TObject, den „Vater“ aller Delphi-Objekte, finden sich in der Delphi-Hilfe. Unter .NET entspricht TObject der Klasse System.Object.
Und so wird eine Instanz erzeugt:

 Objektreferenz := Klasse.Create;

In unserem konkreten Fall also:

MeinAuto := TAuto.Create;

Damit wird Speicherplatz für alle Attribute einer Auto-Instanz im Hauptspeicher reserviert und die zugehörige Adresse in der Variablen MeinAuto gespeichert.
Beim Erzeugen einer Klasse werden die jeweils vorhandenen Attribute mit folgenden Startwerten belegt:

  • Alle Datenfelder mit einem ganzzahligen Datentyp (z.B. Integer) werden mit 0 initialisiert.
  • Alle Datenfelder mit einem String-Typ werden durch eine leere Zeichenkette initialisiert.
  • Alle Datenfelder mit einem Zeigertyp werden mit dem Wert nil initialisiert.
  • Alle anderen Datenfelder bleiben undefiniert!

Wie auf Attribute und Methoden der neu erzeugten Instanz zugegriffen wird, folgt unter „Zugriff auf Objekte“.
create kann übrigens auch an einer Objektreferenz aufgerufen werden. Dadurch wird dann keine neue Instanz erzeugt, sondern es wird das Objekt neu initialisiert.

Eigener Konstruktor

Es ist möglich, für jede Klasse einen eigenen Konstruktor sowie einen eigenen Destruktur zu schreiben. Besonders bei Konstruktoren kann das sinnvoll sein, weil der von TObject geerbte Konstruktor create nicht unbedingt das ausführt, was man sich wünscht.
Eigene Konstruktoren können ebenfalls create heißen, wodurch das ursprüngliche create überdeckt wird – sie können aber auch ganz andere Namen haben.
Das Kennzeichen eines Konstruktors ist, dass er an einer Klasse (nicht an einer Instanz) aufgerufen wird, wodurch eine Instanz erzeugt wird (Reservierung von Speicher usw.). Der Aufruf normaler Methoden, die mit procedure oder function beginnen, ist erst möglich, wenn eine Instanz existiert. Um dies zu unterschieden, beginnt die Deklaration eines Konstruktors mit dem Schlüsselwort constructor:

interface

type
  TAngestellter = class(TMensch)
  private
    FKontoNr: integer;
    FBankleitzahl: integer;
  public
    procedure GehaltZahlen;
    constructor Create(Konto, BLZ: integer);
  end;

implementation

constructor TAngestellter.Create(Konto, BLZ: integer);
begin
  inherited Create; //hierdurch wird der ursprüngliche Konstruktor aufgerufen
  {
    nun können Initialisierungen vorgenommen werden
    im Beispiel haben wir dem Konstruktor gleich Daten für Kontonr. und
    Bankleitzahl übergeben
  }
  FKontoNr := Konto;
  FBankleitzahl := BLZ;
end;

...

var Mitarbeiter: TAngestellter;
...
  Mitarbeiter := TAngestellter.Create(12345, 1234567890); //Dies ruft den neuen Konstruktor auf

Wird der Konstruktor (create) nicht an einer Klasse, sondern an einer Instanz aufgerufen, werden dadurch alle Datenfelder des Objekts mit Standardwerten überschrieben.

Anders als in C++ übernimmt der Konstruktor in Delphi neben der Initialisierung der Klasse auch die Aufgabe der Speicherreservierung. Der Vorfahrkonstruktor steht nun vor dem Problem, dass er wissen muss, dass die Speicherreservierung bereits durchgeführt wurde und somit kein weiterer Speicher zu reservieren ist. In Delphi wurde dies gelöst, indem der Konstruktor eine „Hybrid-Methode“ ist. Im Gegensatz zu normalen Methoden generiert der Compiler Code, der dem Konstruktor zwei versteckte Parameter übergibt. Der Typ des ersten Parameters „Self“ hängt dabei vom Wert des Zweiten ab. Dieser wird im CPU Register DL übergeben und gibt den Aufrufmodus des Konstruktors an. Delphi kennt drei unterschiedliche Aufrufmodi für Konstruktoren.

Der erste Aufrufmodus ist „ClassCreate with Allocation“ und wird beim Erzeugen einer Instanz der Klasse genutzt.

Reference := MyClass.Create;

Der Aufrufmodus-Parameter hat in diesem Fall den Wert 1. Dies führt dazu, dass als erstes die System-Funktion _ClassCreate, mit dem ClassType Self-Parameter, vom Konstruktor aus aufgerufen wird. Diese ruft ihrerseits die Klasssenmethode NewInstance auf, die den notwendigen Speicher reserviert und die Methode InitInstance aufruft. Diese wiederum initialisiert alle Felder des Objekts mit deren Null-Wert und baut die Interface Table auf. Zudem setzt sie den Zeiger auf die VMT (Virtual Method Table) in den dafür vorgesehenen Speicherbereich der Instanz ein. _ClassCreate richtet des Weiteren einen Exception-Block ein, der bei einer Exception innerhalb des Konstruktor-Codes automatisch den Destruktor aufgeruft. Dieser Exception-Block endet mit dem Verlassen des äußersten Konstruktors. Als Rückgabewert liefert_ClassCreate den mit NewInstance erzeugten Instanz-Zeiger, auch bekannt als Self, der nun im Konstruktor wie bei einer normalen Methode Verwendung findet.

Der zweite Aufrufmodus ist „Inherited Call“. Er dient dazu, den Vorfahrkonstruktor aufzurufen.

inherited Create;

Dieser darf natürlich keinen neuen Speicherplatz reservieren und auch die Klassen nicht neu initialisieren. Ein weiterer Exception-Block ist ebenfalls nicht notwendig. Damit bei dem „inherited“ Aufruf dies alles nicht geschieht, setzt der Compiler den Aufrufmodus-Parameter auf den Wert 0, was den Vorfahrkonstruktor veranlasst, den _ClassCreate Aufruf zu übergehen. Der Self-Parameter des Konstruktors ist in diesem Modus ein ganz gewöhnlicher Instanz-Zeiger, da das Objekt ja bereits erzeugt ist.

Der dritte Aufrufmodus ist „Initialization without Allocation“, bei dem zwar ein Exception-Block eingerichtet wird, aber keine Speicherreservierung stattfindet. Dieser Modus dient dem Aufruf des Konstruktors als gewöhnliche Methode.

Variable.Create;

Hierbei enthält der Aufrufmodus-Parameter den Wert -1 und Self ist ein Instanz-Zeiger. Dieser Aufrufmodus wird sehr selten eingesetzt, da es normalerweise nicht notwendig ist, den Konstruktor als reine Initialisierungsmethode zu verwenden, weil er bereits beim Erzeugen der Instanz ausgeführt wurde. Sie ist auf das TurboPascal object zurückzuführen, bei dem man, wie unter C++, den Speicher für dynamische Instanzen selbst reservieren musste. Zudem birgt diese Art des Aufrufs die Gefahr von Speicherlecks, die dadurch entstehen, dass bereits reservierter Speicher und Referenzen auf Objekte überschrieben und somit nicht mehr freigegeben werden können.

Bei allen drei Aufrufmodi wird nach dem Ausführen des Konstruktor-Codes die virtuelle Methode AfterConstruction ausgeführt.

Da dem Konstruktor zwei versteckte Parameter übergeben werden, welche die CPU-Register EAX (ClassType bzw. Self) und DH (Aufrufmodus) belegen, bleibt nur noch das ECX Register für einen dritten Parameter übrig. Der Rest muss über den langsameren Stack übergeben werden.

Objekte freigeben: Der Destruktor

Wird eine Instanz einer Klasse nicht mehr benötigt, sollte der dafür verwendete Hauptspeicher wieder freigegeben werden. Dies geschieht mit dem Gegenstück zum Konstruktor, dem Destruktor („Zerstörer“). Destroy heißt dieser bei von TObject abgeleiteten Klassen. Um ein Objekt freizugeben, sollte jedoch die Methode Free verwendet werden. Free prüft, ob ein Verweis auf nil vorliegt, anschließend wird Destroy aufgerufen. Die Objektreferenz verweist jetzt allerdings nicht unbedingt auf nil. Soll das der Fall sein, kann auf die Prozedur FreeAndNil (Unit SysUtils) zurückgegriffen werden, die das freizugebende Objekt als Parameter erwartet – es handelt sich hier nicht um eine Methode.