Destruktor

Im Projekt, das wir uns im Kapitel zuvor angesehen haben (Information Hidding: Projektdownload), gibt es ein Problem. Schauen wir uns das Hauptprogramm nochmals an:

int main( void )
{
  ...
  myFile = new File( "example.txt" );
  ... 
  myFile->Close();
  delete myFile;
  ...
}

Vor dem Löschen der Instanz myFile muss erst das File geschlossen werden. Dies könnte man auch leicht vergessen - und genau solche Fehler führen in C häufig zu Problemen, wenn zum Beispiel vergessen wird Speicher freizugeben.

Um diesem Problem zu begegnen, gibt es in C++ den sogenannten Destruktor. Dies ist eine Funktion, die ein Objekt vor dem Löschen erst abbauen soll, also allen Speicher freigeben und in unserem Fall halt das Filehandle freigeben.

Der Destruktor hat immer den Namen der Klasse, dem eine Tilde vorangeht. In Beispiel der Klasse File heißt der Destruktor also ~File. Ein Destruktor hat niemals Argumente und kann auch nicht überladen werden.

Ein Destruktor muss in der Klassendeklaration genannt werden:

#include <stdio.h>
 
class File
{
private:
  FILE       * Handle;
  char const * Filename;
  unsigned int Size;
 
public:
  File( char const * filename );
  ~File();                        // Destruktor
 
  bool Read( char * buffer, int size );
  bool Write( char * buffer, int size );
 
  unsigned int GetSize();
 
  bool Close();
};

Nun implementieren wir den Destruktor im Quelltext File.cpp:

File::~File()
{
  Close();
}

Das Explizite schließen der Datei ist nun nicht mehr erforderlich, wir können das Hauptprogramm kürzen:

int main( void )
{
  ...
  myFile = new File( "example.txt" );
  ... 
  delete myFile;
  ...
}

RAII

Diese Klasse ist nun so implementiert, dass sie ihre Ressourcen im Konstruktor anfordert und im Destruktor wieder freigibt. Dies wird 'Resource Acquisition is Initialization' - kurz RAII - genannt und ist ein Entwurfsmuster, dass in C++ gerne verwendet wird, um sicherzustellen, dass Ressourcen wieder freigegeben werden.

Hierfür wird die Variable auf dem Stack angelegt - also nicht per new erstellt. Eine Variable, die auf dem Stack angelegt wird, wird beim Verlassen des Scopes abgebaut, also auch der Destruktor der Variablen aufgerufen:

Also legen wir die Variable einfach auf den Stack:

#include "file.h"
#include <stdio.h>
#include <stdlib.h>
 
int main( void )
{
  File myFile( "example.txt" );
 
  printf( "Datei ist %d Bytes groß\n", myFile.GetSize() );
 
  return EXIT_SUCCESS;
}

Das ist nicht nur sicherer, sondern bedeutet gleichzeitig, dass weniger Quelltext geschrieben werden muss.

Das Ganze gibt es auch als Projekt zum Download.