webentwicklung-frage-antwort-db.com.de

Expliziter Aufruf an Destruktor

Ich bin auf den folgenden Codeausschnitt gestoßen:

#include <iostream>
#include <string>
using namespace std;
class First
{
    string *s;
    public:
    First() { s = new string("Text");}
    ~First() { delete s;}
    void Print(){ cout<<*s;}
};

int main()
{
    First FirstObject;
    FirstObject.Print();
    FirstObject.~First();
}

Der Text besagte, dass dieses Snippet einen Laufzeitfehler verursachen sollte. Da ich mir da nicht ganz sicher war, habe ich versucht, es zu kompilieren und auszuführen. Es funktionierte. Das Seltsame ist, dass das Programm trotz der Einfachheit der Daten nach dem Drucken von "Text" stotterte und erst nach einer Sekunde beendet wurde.

Ich habe dem Destruktor einen String hinzugefügt, der gedruckt werden soll, da ich nicht sicher war, ob es legal ist, einen solchen Destruktor explizit aufzurufen. Das Programm druckte zweimal die Zeichenfolge. Ich vermute also, dass der Destruktor zweimal aufgerufen wird, da der normale Programmabschluss den expliziten Aufruf nicht kennt und versucht, das Objekt erneut zu zerstören.

Eine einfache Suche bestätigte, dass das explizite Aufrufen eines Destruktors für ein automatisiertes Objekt gefährlich ist, da der zweite Aufruf (wenn das Objekt den Gültigkeitsbereich verlässt) undefiniertes Verhalten aufweist. Ich hatte also Glück mit meinem Compiler (VS 2017) oder diesem spezifischen Programm.

Ist der Text über den Laufzeitfehler einfach falsch? Oder ist es wirklich üblich, Laufzeitfehler zu haben? Oder hat mein Compiler vielleicht eine Art Schutzmechanismus gegen solche Dinge implementiert?

21
Andrea Bocco

Eine einfache Suche bestätigte, dass das explizite Aufrufen eines Destruktors für ein automatisiertes Objekt gefährlich ist, da der zweite Aufruf (wenn das Objekt den Gültigkeitsbereich verlässt) undefiniertes Verhalten aufweist.

Das ist wahr. Nicht definiertes Verhalten wird aufgerufen, wenn Sie ein Objekt mit automatischem Speicher explizit löschen. Erfahren Sie mehr darüber .

Ich hatte also Glück mit meinem Compiler (VS 2017) oder diesem spezifischen Programm.

Ich würde sagen, Sie hatten Pech . Das Beste (für Sie als Programmierer), was mit UB passieren kann, ist ein Absturz beim ersten Start. Wenn es gut zu funktionieren scheint, könnte der Absturz am 19. Januar 2038 in der Produktion passieren.

Ist der Text über den Laufzeitfehler einfach falsch? Oder ist es wirklich üblich, Laufzeitfehler zu haben? Oder hat mein Compiler vielleicht eine Art Schutzmechanismus gegen solche Dinge implementiert?

Ja, der Text ist irgendwie falsch. Undefiniertes Verhalten ist undefiniert . Ein Laufzeitfehler ist nur eine von vielen Möglichkeiten (einschließlich Nasendämonen).

Eine gute Lektüre über undefiniertes Verhalten: Was ist undefiniertes Verhalten?

34
YSC

Nein, dies ist einfach undefiniertes Verhalten aus dem Entwurf des C++ - Standards [class.dtor] p16 :

Sobald ein Destruktor für ein Objekt aufgerufen wird, ist das Objekt nicht mehr vorhanden. Das Verhalten ist undefiniert, wenn der Destruktor für ein Objekt aufgerufen wird, dessen Lebensdauer abgelaufen ist ([basic.life]). [Beispiel: Wenn der Destruktor für ein automatisches Objekt explizit aufgerufen wird und der Block anschließend auf eine Weise verlassen wird, die normalerweise die implizite Zerstörung des Objekts auslöst, ist das Verhalten undefiniert. - Ende Beispiel

und wir können aus dem Definition von undefiniertem Verhalten sehen:

verhalten, für das dieses Dokument keine Anforderungen stellt

Sie können keine Erwartungen an die Ergebnisse haben. Möglicherweise hat sich der Autor auf seinem Compiler mit bestimmten Optionen auf einem bestimmten Computer so verhalten, aber wir können nicht erwarten, dass es sich um ein portables oder zuverlässiges Ergebnis handelt. Obwohl es Fälle gibt, in denen die Implementierung versucht, ein bestimmtes Ergebnis zu erzielen , ist dies nur eine andere Form von akzeptablem undefiniertem Verhalten.

Zusätzlich gibt [class.dtor] p15 mehr Kontext zu dem normativen Abschnitt, den ich oben zitiere:

[Anmerkung: Explizite Aufrufe von Destruktoren werden selten benötigt. Eine Verwendung solcher Aufrufe ist für Objekte, die unter Verwendung eines Placement-New-Expression an bestimmten Adressen platziert werden. Eine solche Verwendung der expliziten Platzierung und Zerstörung von Objekten kann erforderlich sein, um dedizierte Hardwareressourcen zu bewältigen und Speicherverwaltungsfunktionen zu schreiben. Beispielsweise,

void* operator new(std::size_t, void* p) { return p; }
struct X {
  X(int);
  ~X();
};
void f(X* p);

void g() {                      // rare, specialized use:
  char* buf = new char[sizeof(X)];
  X* p = new(buf) X(222);       // use buf[] and initialize
  f(p);
  p->X::~X();                   // cleanup
}

- Endnote]

15
Shafik Yaghmour

Ist der Text über den Laufzeitfehler einfach falsch?

Es ist falsch.

Oder ist es wirklich üblich, Laufzeitfehler zu haben? Oder hat mein Compiler vielleicht eine Art Schutzmechanismus gegen solche Dinge implementiert?

Sie können nicht wissen, und dies passiert, wenn Ihr Code ndefiniertes Verhalten ; Sie wissen nicht, was passieren wird, wenn Sie es ausführen.

In deinem Fall hattest du (Un) Glück* und es hat funktioniert, während es für mich ein Fehler (doppelt frei) verursachte.


* Denn wenn Sie einen Fehler erhalten, würden Sie mit dem Debuggen beginnen. Andernfalls könnten Sie beispielsweise in einem großen Projekt den Fehler verpassen ...

9
gsamaras