new und delete

Wie wir gesehen haben, muss ein Array in C++ immer eine feste, also statische, Größe besitzen. Wenn wir diese Limitierung umgehen möchten, haben wir zwei Möglichkeiten.

  • Wir verwalten einen Array in beliebiger Größe von Hand
  • Wir verwenden die Standard Template Library

Zur zweiten Lösung kommen wir später.

Ein dynamisches Array

Sehen wir uns an, wie wir ein Array dynamischer, also beliebiger, Größe von Hand erzeugen und löschen können:

void dynamicArray(unsigned int size)
{
    // Laufvariable deklarieren
    unsigned int i;

    // Array im Speicher anlegen
    int* a = new int[size];

    // Array initialisieren
    for (i = 0; i < size; i++)
    {
        a[i] = int(i);
    }

    for (i = 0; i < size; i++)
    {
        // Werte von a ausgeben
        if (i+1 < size)
            cout << a[i] << " | ";
        else
            cout << a[i] << endl;
    }

    // Array löschen (die eckigen Klammern beachten)
    delete[] a;
}

Wir deklarieren zuerst einen Zeiger (bzw. ein leeres Array) a. Mit Hilfe des Befehls new int[size] erzeugen wir ein Integer-Array der Größe size und weisen es a zu. Am Ende der Funktion löschen wir unser Array mit dem delete[] Befehl. Zwischen diesen beiden Befehlen können wir wie gewohnt auf unser Array zugreifen.

Dynamische Objekte

Zu etwas einfacherem. new und delete können auch verwendet werden, um einzelne Objekte zu erzeugen und wieder zu löschen. Um zu sehen, was da passiert, verwenden wir eine einfache Klasse:

class CObject
{
  public:

    CObject()    { cout << "Konstruktor" << endl; }
    ~CObject()   { cout << "Destruktor"  << endl; }
    void print() { cout << "Print"       << endl; }
};

Hier sind die Funktionen inline deklariert, d.h. nicht separat in einer eigenen C++-Datei. inline-Funktionen werden vom Compiler durch cut-and-paste direkt in den Programmcode, der sie verwendet, eingefügt. Das bietet unter Umständen Geschwindigkeitsvorteile. In unserem Fall sind die Funktionen direkt in der Klasse implementiert, um den Code kurz und übersichtlich zu halten.

Das Objekt gibt bei Konstruktion und Destruktion etwas aus und besitzt über dies hinaus die Funktion print, die von außen aufgerufen werden kann. Sehen wir und also an, was passiert, wenn wir ein Objekt erzeugen, darauf zugreifen und es wieder löschen.

void dynamicObject()
{
    // einzelnes Objekt erzeugen
    CObject* o = new CObject();

    // Objektfuntkion aufrufen
    (*o).print();
    // Dasselbe in einfacherer Notation
    o->print();

    // Objekt löschen (ohne eckige Klammern)
    delete o;
}

Beim Erzeugen durch new wird zunächst Speicher für das Objekt angelegt und danach der Konstruktor aufgerufen. In unserem Fall wird der Standardkonstruktor aufgerufen, also ein Konstruktor ohne Argumente. Anschließend besitzen wir einen Zeiger auf unser Objekt.

Um auf die öffentlichen Elemente des Objekts zuzugreifen, verwenden wir danach normalerweise die Notation objekt->element. Dasselbe ließe sich auch als (*objekt).element schreiben, was umständlicher aussieht, aber denselben Zweck erfüllt.

Am Ende der Methode wird das einzelne Objekt durch den Befehl delete gelöscht. Dabei wird zuerst der Destruktor aufgerufen und danach der Speicher des Objekts wieder freigegeben. Da es sich nicht um ein Array handelt, wird der delete-Befehl ohne eckige Klammern verwendet.

Worin liegt der Unterschied? Dazu das letzte Beispiel.

Objekt-Arrays

void objectArray(unsigned int size)
{
    // Array von Objekten erzeugen
    // (Konstruktor wird aufgerufen)
    CObject* o = new CObject[size];

    // auf Objekte zugreifen
    for (unsigned int i = 0; i < size; i++)
    {
        o[i].print();
    }

    // Objekte löschen (mit eckigen Klammern)
    // (Destruktoren werden aufgerufen)
    delete[] o;
}

Für die neu erzeugten Objekte unseres Arrays wird nach der dynamischen Speicherbelegung jeweils der Standardkonstruktor aufgerufen. Auf die Elemente können wir dann über die normale Pointer-Notation mit zwischengestelltem Punkt zugreifen.

Gelöscht wird das Array mit delete[], also mit eckigen Klammern wie beim einfachen Array davor. Hierbei wird zunächst für jedes Objekt der Destruktor aufgerufen und danach der Speicher des gesamten Arrays wieder freigegeben.

Würden wir die eckigen Klammern hinter delete vergessen, würde der Compiler davon ausgehen, dass es sich um ein einzelnes Objekt handelt!

Selbständige Programmierung
  • Ändere im letzten Beispiel den delete-Operator. Lasse die eckigen Klammern weg, und sieh zu, was passiert.
  • Ändere im vorletzen Beispiel den delete-Operator. Füge Klammern hinzu uns sieh, was passiert.
  • Für die Freaks: Programmiere eine Funktion, welche die Zeiger-Notation *p++ = 0; verwendet, um ein Array mit Nullen zu initialisieren. Lösche danach das Array korrekt mit delete[]. Was ist zu beachten?

zurück