====================================================== Destruktoren und Objekte als Verwalter einer Ressource ====================================================== C++ gehört zu den Programmiersprachen, in der dynamisch belegter Speicher auch explizit wieder freigegeben werden muss. Eine sogenannte _garbage collection_, die dies automatisiert durchführt, steht normalerweise nicht zur Verfügung. Entsprechend gibt es passend zum Operator `new` auch den Operator `delete` in C++. Dynamische Speicherflächen sind eine von vielen denkbaren Ressourcen. Wenn Ressourcen zu verwalten sind, ist es naheliegend, dies mit einem Objekt zu verknüpfen, d.h. * wenn das Objekt erzeugt wird, dann wird die Ressource beschafft und * beim Abbau des Objekts wird die Ressource wieder freigegeben. Ersteres erfolgt (wie bereits demonstriert) im Konstruktor, für letzteres gibt es Destruktoren. Für diese Vorgehensweise gibt es auch einen Namen: _resource acquisition is initialization_ (RAII). Sie wurde für C++ entwickelt, inzwischen aber auch von anderen Programmiersprachen übernommen. Ein besonderer Vorteil von RAII liegt auch in der _exception safeness_, einem Konzept, dem wir später noch begegnen werden. So könnte ein Destruktor für den Datentyp `Matrix` aussehen: ---- CODE (type=cpp) ---------------------------------------------------------- struct Matrix { const std::size_t m; /* number of rows */ const std::size_t n; /* number of columns */ const std::size_t incRow; const std::size_t incCol; double* data; Matrix(std::size_t m, std::size_t n, StorageOrder order) : m(m), n(n), incRow(order == StorageOrder::ColMajor? 1: n), incCol(order == StorageOrder::RowMajor? 1: m), data(new double[m*n]) { } ~Matrix() { delete[] data; } /* ... */ }; ------------------------------------------------------------------------------- Der Destruktor wird ebenfalls nach dem Datentyp benannt (hier `Matrix`), jedoch im Gegensatz zum Konstruktor mit einer Tilde (`~`) davor. Da die Speicherfläche mit einer Dimensionierung in eckigen Klammern belegt wurde, ist für die Freigabe die Angabe von `[]` beim `delete`-Operator notwendig. Der Zeitpunkt des Aufrufs des Destruktors ergibt sich daraus, wie das Objekt angelegt wurde: * Bei globalen Variablen wird der Destruktor beim Ende der Programmausführung aufgerufen. * Bei lokalen Variablen erfolgt der Aufruf am Ende des umgebenden Blocks. Das geschieht auch dann, wenn der Block wegen einer Ausnahmenbehandlung (_exception handling_) abrupt verlassen wird. * Bei dynamisch belegten Objekten wird der Destruktor von `delete` aufgerufen. * Temporäre Objekte werden unmittelbar nach der Auswertung innerhalb eines Ausdrucks abgebaut. D.h. wenn ein Destruktor angegeben wurde, ist sichergestellt, dass dieser immer zu einem wohldefinierten Zeitpunkt aufgerufen wird. Damit das alles gut geht, ist auch darauf zu achten, dass ein Zeiger oder eine Referenz auf eine Ressource nicht versehentlich vervielfältigt wird. So sehr wir auch darauf achten möchten, dass angelegte Ressourcen wieder freigegeben werden, ist es auch wichtig, dass kein Versuch unternommen wird, Ressourcen nach der Freigabe weiterzuverwenden oder mehrfach freizugeben. Aufgabe ======= Ergänzen Sie in der Vorlage den Destruktor mit einer Testausgabe und ergänzen Sie die Vorlage um alle vier oben genannten Szenarien, d.h. erzeugen Sie ein globales, lokales, dynamisch erzeugtes und temporäres Objekt. Vorlage ======= :import: session7/matrix_class7.cpp :navigate: up -> doc:index back -> doc:session7/page08 next -> doc:session7/page10