HOWTO · C++
Expliziter Aufruf eines Destruktors in C++
Dieser Artikel zeigt, wie ein Destruktor explizit in C++ aufgerufen wird
Auf dieser Seite
In C++ rufen Sie den Destruktor eines Objekts explizit mit obj.~ClassName() auf. Dadurch endet die Lebensdauer des Objekts. Verwenden Sie es danach nicht weiter, außer an seinem Speicher wird ein neues Objekt erstellt. Explizite Aufrufe sind selten nötig; die normale, an den Gültigkeitsbereich gebundene Bereinigung oder ein Besitztyp wie std::unique_ptr ist meist sicherer.
Mit der Notation obj.~ClassName() eine Destruktorfunktion explizit aufrufen
Ein Destruktor gibt die Ressourcen frei, die ein Objekt besitzt, wenn dessen Lebensdauer endet. Der Compiler ruft ihn automatisch auf, wenn ein automatisches Objekt seinen Gültigkeitsbereich verlässt. Bei einem Zeiger, der von new zurückgegeben wurde, ruft delete den Destruktor auf und gibt anschließend den Speicher des Objekts frei. Diese normalen Abläufe dürfen nicht mit einem zusätzlichen manuellen Aufruf kombiniert werden: Wird ein automatisches Objekt explizit zerstört und verlässt es anschließend seinen Gültigkeitsbereich, würde erneut versucht, ein Objekt zu zerstören, dessen Lebensdauer bereits beendet ist. Das Verhalten ist undefiniert; es werden nicht zuverlässig zwei Meldungen ausgegeben. Ebenso ist es keine gültige Bereinigungsfolge, ein mit new erstelltes Objekt manuell zu zerstören und später denselben Zeiger an delete zu übergeben.
Ein expliziter Aufruf ist bei sorgfältig verwaltetem Low-Level-Code sinnvoll, etwa wenn sich ein Objekt in vom Aufrufer bereitgestelltem Speicher befindet und das Programm dort ein anderes Objekt erstellen soll. Das folgende Beispiel verwendet Placement-new, um die Lebensdauer eines Objekts in einem Bytepuffer zu beginnen, ruft dessen Destruktor auf und erstellt anschließend an derselben Adresse ein Ersatzobjekt. Der letzte explizite Aufruf beendet die Lebensdauer des Ersatzobjekts; der Bytepuffer selbst zerstört darin gespeicherte Objekte nicht automatisch.
Für den Aufruf folgen auf den Ausdruck des Objekts .~ und der Klassenname: Für ein Objekt namens obj der Klasse Widget lautet die Form obj.~Widget(). Der Destruktor hat keinen Rückgabewert und nimmt keine Argumente entgegen. Die Zugriffsregeln gelten weiterhin: Ein privater Destruktor kann nicht aus Code aufgerufen werden, der keinen Zugriff darauf hat. Der Aufruf führt die Bereinigung des Destruktors aus, gibt aber keinen von einem Allokator oder new erhaltenen Speicher frei. Die Speicherverwaltung ist eine separate Aufgabe.
Im Beispiel sorgt alignas(Widget) dafür, dass der Bytepuffer die für ein Widget erforderliche Ausrichtung hat; seine Größe reicht für eine Instanz aus. Placement-new erstellt das Objekt in diesem vorhandenen Puffer, statt eine separate Speicherreservierung anzufordern. Der erste explizite Aufruf beendet die Lebensdauer der ersten Instanz, bevor das Ersatzobjekt erstellt wird. Auch das Ersatzobjekt wird anschließend explizit zerstört. Da es sich bei dem Speicher um ein lokales Bytearray handelt, endet beim Verlassen seines Gültigkeitsbereichs nur die Lebensdauer des Arrays; für ein darin platziertes Widget wird kein Destruktor aufgerufen. Bei diesem Vorgehen muss das Programm nachverfolgen, welches Objekt sich gerade im Speicher befindet, und sicherstellen, dass jedes erstellte Objekt genau einmal zerstört wird.
Für gewöhnlichen Anwendungscode ist eine lokale Variable einfacher: Ihr Destruktor wird aufgerufen, wenn die Ausführung den Block verlässt, auch wenn eine Funktion zurückkehrt. Ein std::unique_ptr<Widget> eignet sich, wenn dynamischer Besitz erforderlich ist, da er die Zerstörung des Objekts an seine eigene Lebensdauer bindet. Ist ein Objekt Teil eines Containers oder einer anderen Klasse, sollte der Besitzer die Zerstörung verwalten, statt den Destruktor eines enthaltenen Objekts von Hand aufzurufen. So ist die Bereinigung in der Programmstruktur erkennbar und das Objekt wird nicht nach dem Ende seiner Lebensdauer verwendet.
Führen Sie dieses Beispiel aus:
#include <iostream>
#include <new>
#include <string>
#include <utility>
class Widget {
public:
explicit Widget(std::string name) : name_(std::move(name)) {
std::cout << "created " << name_ << '\n';
}
~Widget() { std::cout << "destroyed " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
alignas(Widget) unsigned char storage[sizeof(Widget)];
Widget* obj = ::new (static_cast<void*>(storage)) Widget("first");
obj->~Widget();
obj = ::new (static_cast<void*>(storage)) Widget("second");
obj->~Widget();
}
created first
destroyed first
created second
destroyed second
Wenn der Code Speicher und Lebensdauern nicht direkt verwaltet, lassen Sie den Gültigkeitsbereich eines lokalen Objekts auf natürliche Weise enden. Bei dynamischem Besitz ist std::unique_ptr vorzuziehen; sein Destruktor verwendet delete, wenn der Besitzer seinen Gültigkeitsbereich verlässt. Greifen Sie nicht auf einen Zeiger zu, nachdem das zugehörige Objekt zerstört wurde, und rufen Sie einen Destruktor nicht manuell auf, nur um eine Member-Ressource freizugeben. Verwenden Sie stattdessen einen geeigneten Besitzer oder Ressourcen-Wrapper aus der Standardbibliothek.