it-swarm.com.de

Ausnahmen aus einem Destruktor werfen

Die meisten Leute sagen , niemals eine Ausnahme aus einem Destruktor zu werfen - dies führt zu undefiniertem Verhalten. Stroustrup macht den Punkt, dass "der Vektordestruktor explizit den Destruktor für jedes Element aufruft. Dies impliziert, dass die Vektordestruktion fehlschlägt, wenn ein Elementdestruktor auslöst ... Es gibt wirklich keinen guten Weg, um zu schützen gegen Ausnahmen, die von Destruktoren ausgelöst werden, gibt die Bibliothek keine Garantie, wenn ein Elementdestruktor ausgelöst wird "(aus Anhang E3.2) .

Dieser Artikel scheint etwas anderes zu sagen - dass das Werfen von Destruktoren mehr oder weniger in Ordnung ist.

Meine Frage ist also: Wenn das Werfen von einem Destruktor zu undefiniertem Verhalten führt, wie gehen Sie mit Fehlern um, die während eines Destruktors auftreten?

Wenn während eines Bereinigungsvorgangs ein Fehler auftritt, ignorieren Sie ihn einfach? Ist es nicht sinnvoll, eine Ausnahme aus dem Destruktor zu werfen, wenn es sich um einen Fehler handelt, der möglicherweise auf dem Stack, aber nicht direkt im Destruktor behandelt werden kann?

Offensichtlich sind solche Fehler selten, aber möglich.

240
Greg Rogers

Eine Ausnahme aus einem Destruktor zu werfen ist gefährlich.
Wenn eine andere Ausnahme bereits verbreitet wird, wird die Anwendung beendet.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

Dies läuft im Wesentlichen auf Folgendes hinaus:

Alles, was gefährlich ist (d. H. Eine Ausnahme auslösen könnte), sollte mit öffentlichen Methoden erfolgen (nicht unbedingt direkt). Der Benutzer Ihrer Klasse kann dann möglicherweise mit den öffentlichen Methoden auf diese Situationen reagieren und mögliche Ausnahmen abfangen.

Der Destruktor beendet das Objekt, indem er diese Methoden aufruft (falls der Benutzer dies nicht explizit getan hat), aber alle Ausnahmen werden abgefangen und gelöscht (nachdem versucht wurde, das Problem zu beheben).

Tatsächlich geben Sie die Verantwortung also an den Benutzer weiter. Wenn der Benutzer in der Lage ist, Ausnahmen zu korrigieren, ruft er die entsprechenden Funktionen manuell auf und verarbeitet alle Fehler. Wenn der Benutzer des Objekts nicht besorgt ist (da das Objekt zerstört wird), ist der Destruktor für das Geschäft zuständig.

Ein Beispiel:

std :: fstream

Die Methode close () kann möglicherweise eine Ausnahme auslösen. Der Destruktor ruft close () auf, wenn die Datei geöffnet wurde, stellt jedoch sicher, dass keine Ausnahmen aus dem Destruktor übertragen werden.

Wenn der Benutzer eines Dateiobjekts eine spezielle Behandlung für Probleme beim Schließen der Datei durchführen möchte, ruft er close () manuell auf und behandelt alle Ausnahmen. Wenn sie sich andererseits nicht darum kümmern, wird der Destruktor die Situation selbst in die Hand nehmen.

Scott Myers hat einen ausgezeichneten Artikel zu diesem Thema in seinem Buch "Effective C++".

Bearbeiten:

Anscheinend auch in "Effective C++"
Punkt 11: Verhindern, dass Ausnahmen Destruktoren verlassen

183
Martin York

Das Herauswerfen eines Destruktors kann zu einem Absturz führen, da dieser Destruktor als Teil des "Stack Unwinding" bezeichnet werden kann. Stack Unwinding ist eine Prozedur, die ausgeführt wird, wenn eine Ausnahme ausgelöst wird. Bei dieser Prozedur werden alle Objekte, die seit dem "try" und bis zum Auslösen der Exception in den Stack verschoben wurden, beendet -> ihre Destruktoren werden aufgerufen. Während dieses Vorgangs ist ein weiterer Ausnahmewurf nicht zulässig, da nicht zwei Ausnahmen gleichzeitig behandelt werden können. Dies führt zu einem Aufruf von abort (), das Programm stürzt ab und die Steuerung kehrt zum Betriebssystem zurück.

52
Gal Goldman

Wir müssen hier unterscheiden , anstatt blind allgemein Ratschläge für spezifisch Fälle zu befolgen .

Beachten Sie, dass Folgendes ignoriert das Problem von Objektcontainern und was zu tun ist, wenn sich mehrere Objekte in Containern befinden. (Und es kann teilweise ignoriert werden, da einige Objekte einfach nicht gut in einen Container passen.)

Das ganze Problem wird leichter zu denken, wenn wir Klassen in zwei Typen aufteilen. Ein Klassendichter kann zwei verschiedene Verantwortlichkeiten haben:

  • (R) Release-Semantik (auch bekannt als "free that memory")
  • (C) commit Semantik (aka flush Datei auf Festplatte)

Wenn wir die Frage so betrachten, kann man meiner Meinung nach argumentieren, dass (R) Semantik niemals eine Ausnahme von einem dtor verursachen sollte, da es a) nichts gibt, was wir dagegen tun können, und b) viele Operationen mit freien Ressourcen dies nicht tun sogar eine Fehlerprüfung vorsehen, z voidfree(void* p);.

Objekte mit (C) Semantik, wie ein Dateiobjekt, dessen Daten erfolgreich gelöscht werden müssen, oder eine ("scope guarded") Datenbankverbindung, die ein Commit im dtor ausführt, sind anderer Art: We can Tun Sie etwas gegen den Fehler (auf Anwendungsebene) und wir sollten wirklich nicht so weitermachen, als ob nichts passiert wäre.

Wenn wir der RAII-Route folgen und Objekte mit (C) -Semantik in ihren D'toren zulassen, müssen wir meines Erachtens auch den merkwürdigen Fall berücksichtigen, in dem solche D'toren werfen können. Daraus folgt, dass Sie solche Objekte nicht in Containern ablegen sollten und dass das Programm weiterhin terminate() kann, wenn ein Commit-Dtor ausgelöst wird, während eine andere Ausnahme aktiv ist.


In Bezug auf die Fehlerbehandlung (Commit/Rollback-Semantik) und Ausnahmen spricht man gut Andrei Alexandresc : Fehlerbehandlung in C++/Declarative Control Flow (gehalten am NDC 2014 )

Im Detail erklärt er, wie die Folly-Bibliothek ein UncaughtExceptionCounter für ihre ScopeGuard Werkzeuge implementiert.

(Ich sollte beachten, dass andere auch ähnliche Ideen hatten.)

Während sich der Vortrag nicht auf das Werfen von einem d'tor konzentriert, zeigt er ein Werkzeug, mit dem man heute das Probleme beim Werfen loswerden kann von einem d'tor.

In dem zukunft, Dort kann ein Standardmerkmal für dieses sein, siehe N3614 , und ein Diskussion darüber .

Upd '17: Die C++ 17-Standardfunktion hierfür ist std::uncaught_exceptions afaikt. Ich zitiere schnell den Artikel von cppref:

Anmerkungen

Ein Beispiel, in dem int- returning uncaught_exceptions Verwendet wird, ist ... ... zuerst ein Guard-Objekt zu erstellen und die Anzahl der nicht erfassten Ausnahmen in seinem Konstruktor aufzuzeichnen. Die Ausgabe wird vom Destruktor des Guard-Objekts ausgeführt, es sei denn, foo () wirft ( in diesem Fall ist die Anzahl der nicht erfassten Ausnahmen im Destruktor größer als die, die der Konstruktor beobachtet hat)

46
Martin Ba

Die eigentliche Frage, die Sie sich über das Werfen aus einem Destruktor stellen sollten, lautet: "Was kann der Aufrufer damit tun?" Gibt es tatsächlich irgendetwas Nützliches, das Sie tun können, mit der Ausnahme, dass die Gefahren, die durch das Werfen von einem Destruktor entstehen, ausgeglichen würden?

Wenn ich ein Foo Objekt zerstöre und der Foo Destruktor eine Ausnahme auslöst, was kann ich dann vernünftigerweise damit machen? Ich kann es protokollieren oder ignorieren. Das ist alles. Ich kann es nicht "reparieren", da das Objekt Foo bereits weg ist. Im besten Fall protokolliere ich die Ausnahme und fahre fort, als ob nichts passiert wäre (oder beende das Programm). Lohnt es sich wirklich, undefiniertes Verhalten durch Werfen von einem Destruktor zu verursachen?

19
Derek Park

Aus dem ISO-Entwurf für C++ (ISO/IEC JTC 1/SC 22 N 4411)

Destruktoren sollten also im Allgemeinen Ausnahmen abfangen und nicht zulassen, dass sie sich aus dem Destruktor ausbreiten.

3 Das Aufrufen von Destruktoren für automatische Objekte, die auf dem Pfad von einem Try-Block zu einem Throw-Ausdruck erstellt wurden, wird als "Stack Unwinding" bezeichnet (15.5.1). Destruktoren sollten also im Allgemeinen Ausnahmen abfangen und nicht zulassen, dass sie sich aus dem Destruktor ausbreiten. - Endnote]

12
lothar

Es ist gefährlich, macht aber auch unter dem Gesichtspunkt der Lesbarkeit/Codeverständlichkeit keinen Sinn.

Was Sie fragen müssen, ist in dieser Situation

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

Was soll die Ausnahme fangen? Soll der Anrufer von foo? Oder sollte foo damit umgehen? Warum sollte sich der Anrufer von foo um ein Objekt innerhalb von foo kümmern? Es mag einen Weg geben, wie die Sprache dies als sinnvoll definiert, aber es wird unlesbar und schwer zu verstehen sein.

Was noch wichtiger ist, wohin geht der Speicher für Object? Wohin geht die Erinnerung, die dem Objekt gehört? Ist es noch vergeben (angeblich, weil der Destruktor ausgefallen ist)? Bedenken Sie auch, dass sich das Objekt im Stapelspeicher befand, sodass es offensichtlich unabhängig davon verschwunden ist.

Dann betrachten Sie diesen Fall

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

Wie lösche ich nach dem Fehlschlagen des Löschvorgangs von obj3 tatsächlich auf eine Weise, die garantiert nicht fehlschlägt? Es ist mein Gedächtnis, verdammt!

Im ersten Codefragment wird nun berücksichtigt, dass Object automatisch ausgeblendet wird, da es sich auf dem Stapel befindet, während sich Object3 auf dem Heap befindet. Da der Zeiger auf Object3 weg ist, sind Sie eine Art SOL. Sie haben einen Speicherverlust.

Ein sicherer Weg, Dinge zu tun, ist der folgende

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

Siehe auch diese FAQ

12
Doug T.

Ihr Destruktor wird möglicherweise in einer Kette anderer Destruktoren ausgeführt. Wenn Sie eine Ausnahme auslösen, die nicht von Ihrem unmittelbaren Aufrufer abgefangen wird, können mehrere Objekte in einem inkonsistenten Zustand verbleiben, wodurch noch mehr Probleme auftreten, als wenn der Fehler bei der Bereinigung ignoriert wird.

7
Franci Penov

Als Ergänzung zu den wichtigsten Antworten, die gut, umfassend und genau sind, möchte ich den Artikel kommentieren, auf den Sie verweisen - der besagt, dass es nicht so schlimm ist, Ausnahmen in Destruktoren zu werfen.

Der Artikel verwendet die Zeile "Welche Alternativen gibt es zum Auslösen von Ausnahmen?" Und listet einige Probleme mit jeder der Alternativen auf. In diesem Fall wird der Schluss gezogen, dass wir weiterhin Ausnahmen auslösen sollten, da wir keine problemlose Alternative finden können.

Das Problem ist, dass keines der Probleme, die es mit den Alternativen auflistet, annähernd so schlimm ist wie das Ausnahmeverhalten, das, wie wir uns erinnern, "undefiniertes Verhalten Ihres Programms" ist. Einige der Einwände des Autors beinhalten "ästhetisch hässlich" und "schlechten Stil fördern". Was hättest du lieber? Ein Programm mit schlechtem Stil oder eines, das undefiniertes Verhalten aufwies?

5
DJClayworth

Alle anderen haben erklärt, warum das Werfen von Destruktoren schrecklich ist ... was können Sie dagegen tun? Wenn Sie einen Vorgang ausführen, der möglicherweise fehlschlägt, erstellen Sie eine separate öffentliche Methode, die eine Bereinigung durchführt und beliebige Ausnahmen auslösen kann. In den meisten Fällen werden Benutzer dies ignorieren. Wenn Benutzer den Erfolg/Misserfolg der Bereinigung überwachen möchten, können sie einfach die explizite Bereinigungsroutine aufrufen.

Beispielsweise:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};
5
Tom

Ich bin in der Gruppe, die der Meinung ist, dass das "Scoped Guard" -Muster, das in den Destruktor geworfen wird, in vielen Situationen nützlich ist - insbesondere für Komponententests. Beachten Sie jedoch, dass in C++ 11 das Einfügen eines Destruktors zu einem Aufruf von std::terminate Führt, da Destruktoren implizit mit noexcept beschriftet sind.

Andrzej Krzemieński hat einen großartigen Beitrag zum Thema Destruktoren, die werfen:

Er weist darauf hin, dass C++ 11 einen Mechanismus zum Überschreiben des Standardwerts noexcept für Destruktoren hat:

In C++ 11 wird ein Destruktor implizit als noexcept angegeben. Auch wenn Sie keine Spezifikation hinzufügen und Ihren Destruktor folgendermaßen definieren:

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

Der Compiler fügt Ihrem Destruktor weiterhin unsichtbar die Spezifikation noexcept hinzu. Dies bedeutet, dass in dem Moment, in dem Ihr Destruktor eine Ausnahme auslöst, std::terminate Aufgerufen wird, auch wenn keine Doppelausnahmesituation vorliegt. Wenn Sie wirklich fest entschlossen sind, Ihren Destruktoren das Werfen zu erlauben, müssen Sie dies explizit angeben. Sie haben drei Möglichkeiten:

  • Gib deinen Destruktor explizit als noexcept(false) an,
  • Vererben Sie Ihre Klasse von einer anderen Klasse, die ihren Destruktor bereits als noexcept(false) angibt.
  • Fügen Sie ein nicht statisches Datenelement in Ihre Klasse ein, das seinen Destruktor bereits als noexcept(false) angibt.

Wenn Sie sich entscheiden, den Destruktor einzusetzen, sollten Sie sich immer des Risikos einer Doppelausnahme bewusst sein (Auslösen, während der Stapel aufgrund einer Ausnahme abgewickelt wird). Dies würde einen Aufruf von std::terminate Verursachen und ist selten das, was Sie wollen. Um dieses Verhalten zu vermeiden, können Sie einfach mit std::uncaught_exception() prüfen, ob bereits eine Ausnahme vorliegt, bevor Sie eine neue auslösen.

4
GaspardP

F: Meine Frage ist also: Wenn das Werfen von einem Destruktor zu undefiniertem Verhalten führt, wie gehen Sie mit Fehlern um, die während eines Destruktors auftreten?

A: Es gibt verschiedene Möglichkeiten:

  1. Lassen Sie die Ausnahmen aus Ihrem Destruktor herausfließen, unabhängig davon, was an anderer Stelle vor sich geht. Seien Sie sich dabei bewusst (oder sogar ängstlich), dass std :: terminate folgen kann.

  2. Lassen Sie niemals Ausnahmen aus Ihrem Destruktor herausfließen. Möglicherweise schreiben Sie in ein Protokoll, wenn Sie können, einen großen, roten, schlechten Text.

  3. mein Favorit: Wenn std::uncaught_exception gibt false zurück, lassen Sie Ausnahmen herausfließen. Wenn dies der Fall ist, greifen Sie auf den Protokollierungsansatz zurück.

Aber ist es gut, d'tors zu werfen?

Ich stimme den meisten der obigen Aussagen zu, dass das Werfen in Destruktoren am besten vermieden wird, wo es möglich ist. Aber manchmal ist es am besten, wenn Sie akzeptieren, dass es passieren kann, und damit gut umgehen. Ich würde 3 oben wählen.

Es gibt ein paar seltsame Fälle, in denen es eigentlich eine großartige Idee ist, von einem Destruktor zu werfen. Wie der "muss überprüfen" Fehlercode. Dies ist ein Werttyp, der von einer Funktion zurückgegeben wird. Wenn der Aufrufer den enthaltenen Fehlercode liest/überprüft, wird der zurückgegebene Wert unbemerkt zerstört. Aber Wenn der zurückgegebene Fehlercode nicht gelesen wurde, bis die Rückgabewerte den Gültigkeitsbereich verlassen, wird eine Ausnahme ausgelöst von seinem Destruktor.

2
MartinP

Ich verfolge derzeit die Richtlinie (die so viele sagen), dass Klassen keine aktiven Ausnahmen von ihren Destruktoren auslösen sollten, sondern stattdessen eine öffentliche "close" -Methode bereitstellen sollten, um den Vorgang auszuführen, der fehlschlagen könnte ...

... aber ich glaube, Destruktoren für Klassen vom Typ Container, wie ein Vektor, sollten keine Ausnahmen maskieren, die von Klassen ausgelöst werden, die sie enthalten. In diesem Fall verwende ich tatsächlich eine "free/close" -Methode, die sich selbst rekursiv aufruft. Ja, sagte ich rekursiv. Es gibt eine Methode für diesen Wahnsinn. Die Weitergabe von Ausnahmen setzt voraus, dass ein Stapel vorhanden ist: Wenn eine einzelne Ausnahme auftritt, werden beide verbleibenden Destruktoren weiterhin ausgeführt, und die ausstehende Ausnahme wird weitergegeben, sobald die Routine zurückkehrt. Dies ist großartig. Wenn mehrere Ausnahmen auftreten, wird (abhängig vom Compiler) entweder diese erste Ausnahme verbreitet oder das Programm wird beendet, was in Ordnung ist. Wenn so viele Ausnahmen auftreten, dass die Rekursion den Stapel überläuft, stimmt etwas nicht und jemand wird es herausfinden, was auch in Ordnung ist. Persönlich irre ich mich auf der Seite von Fehlern, die explodieren, anstatt verborgen, geheim und heimtückisch zu sein.

Der Punkt ist, dass der Container neutral bleibt, und es liegt an den enthaltenen Klassen, zu entscheiden, ob sie sich in Bezug auf das Auslösen von Ausnahmen von ihren Destruktoren verhalten oder verhalten.

1
Matthew

Martin Ba (oben) ist auf dem richtigen Weg - Sie architekten unterschiedlich für RELEASE- und COMMIT-Logik.

Zur Veröffentlichung:

Sie sollten keine Fehler essen. Sie geben Speicher frei, schließen Verbindungen usw. Niemand im System sollte diese Dinge jemals wieder SEHEN, und Sie geben Ressourcen an das Betriebssystem zurück. Wenn Sie hier echte Fehlerbehandlung benötigen, ist dies wahrscheinlich eine Folge von Konstruktionsfehlern in Ihrem Objektmodell.

Für Commit:

Hier möchten Sie die gleiche Art von RAII-Wrapper-Objekten, die Dinge wie std :: lock_guard für Mutexe bereitstellen. Mit diesen schreiben Sie die Festschreibungslogik nicht in den dtor AT ALL. Sie haben eine dedizierte API dafür, verpacken dann Objekte, die RAII in IHRE dtors festschreiben und die Fehler dort behandeln. Denken Sie daran, dass Sie Ausnahmen in einem Destruktor problemlos abfangen können, da dies tödlich ist. Auf diese Weise können Sie Richtlinien und unterschiedliche Fehlerbehandlungen implementieren, indem Sie einfach einen anderen Wrapper erstellen (z. B. std :: unique_lock vs. std :: lock_guard) und sicherstellen Sie werden nicht vergessen, die Festschreibungslogik aufzurufen - das ist die einzige halbwegs angemessene Rechtfertigung dafür, sie an erster Stelle in ein dtor zu setzen.

1
user3726672

Meine Frage lautet also: Wenn das Werfen von einem Destruktor zu undefiniertem Verhalten führt, wie gehen Sie mit Fehlern um, die während eines Destruktors auftreten?

Das Hauptproblem ist: Sie können nicht nicht scheitern. Was bedeutet es schließlich, nicht zu scheitern? Was passiert mit der Integrität unserer Daten, wenn das Festschreiben einer Transaktion in einer Datenbank fehlschlägt und das Festschreiben fehlschlägt (Rollback fehlschlägt)?

Da Destruktoren sowohl für normale als auch für Ausnahmepfade (Fail-Pfade) aufgerufen werden, können sie selbst nicht fehlschlagen. Andernfalls schlagen wir fehl.

Dies ist ein konzeptionell schwieriges Problem, aber häufig besteht die Lösung darin, nur einen Weg zu finden, um sicherzustellen, dass ein Fehlschlagen nicht fehlschlagen kann. Beispielsweise kann eine Datenbank Änderungen vor dem Festschreiben einer externen Datenstruktur oder -datei schreiben. Wenn die Transaktion fehlschlägt, kann die Datei-/Datenstruktur verworfen werden. Dann muss nur sichergestellt werden, dass die Änderungen aus dieser externen Struktur/Datei eine atomare Transaktion werden, die nicht fehlschlagen kann.

Die pragmatische Lösung ist vielleicht nur sicherzustellen, dass die Wahrscheinlichkeit eines Scheiterns bei einem Scheitern astronomisch unwahrscheinlich ist, da es in einigen Fällen fast unmöglich sein kann, Dinge zum Scheitern zu bringen.

Für mich ist die beste Lösung, Ihre Nicht-Bereinigungslogik so zu schreiben, dass die Bereinigungslogik nicht versagen kann. Wenn Sie beispielsweise versucht sind, eine neue Datenstruktur zu erstellen, um eine vorhandene Datenstruktur zu bereinigen, sollten Sie diese Hilfsstruktur im Voraus erstellen, damit wir sie nicht mehr in einem Destruktor erstellen müssen.

Zugegeben, das ist alles viel einfacher gesagt als getan, aber es ist der einzig richtige Weg, wie ich es sehe. Manchmal denke ich, dass es eine Möglichkeit geben sollte, eine separate Destruktorlogik für normale Ausführungspfade zu schreiben, die von außergewöhnlichen Pfaden abweicht, da Destruktoren manchmal das Gefühl haben, dass sie die doppelte Verantwortung haben, wenn sie versuchen, beide zu handhaben (ein Beispiel sind Scope Guards, die eine explizite Entlassung erfordern) ; sie würden dies nicht benötigen, wenn sie außergewöhnliche von nicht außergewöhnlichen Zerstörungspfaden unterscheiden könnten).

Das ultimative Problem ist jedoch, dass wir nicht scheitern müssen, und es ist ein schwieriges konzeptionelles Problem, das in allen Fällen perfekt zu lösen ist. Es wird einfacher, wenn Sie sich nicht zu sehr mit komplexen Kontrollstrukturen auseinandersetzen, in denen Tonnen von kleinen Objekten miteinander interagieren, sondern Ihre Entwürfe etwas voluminöser modellieren (Beispiel: Partikelsystem mit einem Destruktor, um das gesamte Partikel zu zerstören System, kein separater nicht-trivialer Destruktor pro Partikel). Wenn Sie Ihre Entwürfe auf dieser Art von gröberer Ebene modellieren, müssen Sie mit weniger nicht-trivialen Destruktoren umgehen und können sich häufig auch den erforderlichen Speicher-/Verarbeitungsaufwand leisten, um sicherzustellen, dass Ihre Destruktoren nicht ausfallen können.

Und das ist natürlich eine der einfachsten Lösungen, Destruktoren weniger häufig zu verwenden. Im obigen Partikel-Beispiel sollten möglicherweise nach dem Zerstören/Entfernen eines Partikels einige Dinge getan werden, die aus irgendeinem Grund fehlschlagen könnten. In diesem Fall können Sie, anstatt eine solche Logik über den dtor des Partikels aufzurufen, die auf einem außergewöhnlichen Pfad ausgeführt werden kann, alles vom Partikelsystem ausführen lassen, wenn es remove ein Partikel ist. Das Entfernen eines Partikels kann immer auf einem nicht außergewöhnlichen Weg erfolgen. Wenn das System zerstört wird, kann es möglicherweise nur alle Partikel bereinigen und sich nicht um die einzelne Partikelentfernungslogik kümmern, die fehlschlagen kann, während die Logik, die fehlschlagen kann, nur während der normalen Ausführung des Partikelsystems ausgeführt wird, wenn ein oder mehrere Partikel entfernt werden.

Es gibt oft solche Lösungen, die auftauchen, wenn Sie es vermeiden, mit vielen kleinen Objekten mit nicht-trivialen Destruktoren umzugehen. Wo Sie sich in einem Durcheinander verheddern können, in dem es fast unmöglich erscheint, Ausnahmesicherheit zu bieten, ist, wenn Sie sich in vielen jugendlichen Objekten verfangen, die alle nicht triviale Dinge haben.

Es wäre sehr hilfreich, wenn nothrow/noexcept tatsächlich in einen Compilerfehler übersetzt würde, wenn irgendetwas, das es spezifiziert (einschließlich virtueller Funktionen, die die noexcept-Spezifikation seiner Basisklasse erben sollten), versuchte, alles aufzurufen, was werfen könnte. Auf diese Weise könnten wir all dieses Zeug zur Kompilierungszeit abfangen, wenn wir tatsächlich versehentlich einen Destruktor schreiben, der werfen könnte.

0
Dragon Energy

Im Gegensatz zu Konstruktoren, bei denen das Auslösen von Ausnahmen ein nützlicher Hinweis darauf ist, dass die Objekterstellung erfolgreich war, sollten Ausnahmen nicht in Destruktoren ausgelöst werden.

Das Problem tritt auf, wenn während des Abwickelns des Stapels eine Ausnahme von einem Destruktor ausgelöst wird. In diesem Fall befindet sich der Compiler in einer Situation, in der er nicht weiß, ob der Stapelabwicklungsprozess fortgesetzt oder die neue Ausnahme behandelt werden soll. Das Endergebnis ist, dass Ihr Programm sofort beendet wird.

Infolgedessen ist es die beste Vorgehensweise, Ausnahmen in Destruktoren überhaupt nicht zu verwenden. Schreiben Sie stattdessen eine Nachricht in eine Protokolldatei.

0
Devesh Agrawal

Stellen Sie ein Alarmereignis ein. In der Regel sind Alarmereignisse die bessere Art, Fehler beim Bereinigen von Objekten zu melden

0
MRN