Die Leute haben sich ziemlich dagegen ausgesprochen, Ausnahmen von Destruktoren zu werfen. Nehmen Sie diese Antwort als Beispiel. Ich frage mich, ob std::uncaught_exception()
verwendet werden kann, um portabel zu erkennen, ob wir gerade dabei sind, den Stack abzuwickeln aufgrund einer anderen Ausnahme.
Ich finde mich absichtlich Ausnahmen in Destruktoren werfen. Um zwei mögliche Anwendungsfälle zu nennen:
std::exception_ptr
, das eine Ausnahme in einem anderen Thread enthalten könnte. Diese Ausnahmesituationen einfach zu ignorieren fühlt sich einfach falsch an. Und es besteht die Möglichkeit, dass ein Ausnahmebehandler durch das Auslösen einer Ausnahme nützlichere Kontextinformationen bereitstellen kann, als wenn der Destruktor selbst in std::cerr
schreiben würde. Darüber hinaus ist das Auslösen von Ausnahmen für alle fehlgeschlagenen Assertionen ein wichtiger Teil meines Unit-Testing-Ansatzes. Eine Fehlermeldung gefolgt von einer ignorierten Fehlerbedingung würde in diesem Fall nicht funktionieren.
Meine Frage ist also, ist es OK, Ausnahmen auszulösen, außer wenn eine andere Ausnahme verarbeitet wird oder gibt es einen Grund, das nicht zu tun?
Um dies in Code zu setzen:
%Vor%Soweit ich das beurteilen kann, sollte dies sicher und in vielen Fällen besser als gar keine Ausnahme sein. Aber ich möchte das überprüfen.
Herb Sutter hat zu dem Thema geschrieben: Ссылка
Seine Schlussfolgerung lautet, niemals von einem Destruktor zu werfen, sondern den Fehler immer mit dem Mechanismus zu melden, den Sie verwenden würden, wenn Sie nicht werfen können.
Die zwei Gründe sind:
uncaught_exception
true zurück und trotzdem ist es sicher zu werfen. Beachten Sie, dass es bei einem bestimmten wiederverwendbaren Code nicht möglich ist, sicher zu sein, dass er während des Abwickelns des Stacks nie aufgerufen wird. Was auch immer Ihr Code tut, Sie können nicht sicher sein, dass ein bestimmter Benutzer es nicht von einem Destruktor mit einem try/catch
aufrufen möchte, um seine Exceptions zu behandeln. Daher können Sie sich nicht darauf verlassen, dass uncaught_exception
immer true zurückgibt, wenn es sicher ist zu werfen, außer vielleicht, indem Sie die Funktion dokumentieren, "darf nicht von Destruktoren aufgerufen werden". Wenn Sie darauf zurückgreifen, müssten alle Anrufer auch ihre Funktionen dokumentieren, "müssen nicht von Destruktoren aufgerufen werden" und Sie haben eine noch ärgerlichere Einschränkung.
Abgesehen von allem anderen ist die Notlauf-Garantie für Benutzer wertvoll - sie hilft ihnen, ausnahmesicheren Code zu schreiben, wenn sie wissen, dass eine bestimmte Sache, die sie tun, nicht geworfen wird.
Ein Ausweg ist es, Ihrer Klasse eine Mitgliedsfunktion close
zu geben, die trySomeCleanupOperation
aufruft und bei fehlgeschlagenem Fall eine Aufgabe auslöst. Dann ruft der Destruktor trySomeCleanupOperation
auf und protokolliert oder unterdrückt den Fehler, wirft ihn jedoch nicht. Dann können Benutzer close
aufrufen, wenn sie wissen wollen, ob ihre Operation erfolgreich ist oder nicht, und den Destruktor nur behandeln lassen, wenn es ihnen egal ist (einschließlich des Falls, in dem der Destruktor als Teil des Stackabwickelns aufgerufen wird, wegen einer Ausnahme) wurde geworfen, bevor der Aufruf von close
) aufgerufen wurde. "Aha!", Sagst du, "aber das vereitelt den Zweck von RAII, weil der Benutzer daran denken muss, close
!" Aufzurufen. Ja, ein bisschen, aber was in Frage steht, ist nicht, ob RAII alles tun kann, was Sie wollen. Es kann nicht. Was ist in Frage ist, ob es konsistent weniger tut als Sie möchten (Sie möchten, dass es eine Ausnahme auslöst, wenn trySomeCleanupOperator
fehlschlägt), oder weniger überraschend wenn wird beim Abwickeln des Stapels verwendet.
Darüber hinaus ist das Auslösen von Ausnahmen für alle fehlgeschlagenen Assertionen ein wichtiger Teil meines Unit-Test-Ansatzes.
Das ist wahrscheinlich ein Fehler - Ihr Unit-Test-Framework sollte in der Lage sein, einen terminate()
als Testfehler zu behandeln. Nehmen wir an, dass eine Assertion während des Abwickelns des Stacks fehlschlägt - sicherlich möchten Sie dies aufzeichnen, aber Sie können dies nicht tun, indem Sie eine Exception werfen, so dass Sie sich in eine Ecke malten. Wenn Ihre Behauptungen enden, können Sie sie als Beendigungen erkennen.
Leider, wenn Sie beenden, können Sie den Rest der Tests nicht ausführen (zumindest nicht in diesem Prozess). Aber wenn eine Assertion fehlschlägt, befindet sich Ihr Programm im Allgemeinen in einem unbekannten und potenziell unsicheren Zustand. Wenn Sie also eine Assertion nicht bestanden haben, können Sie sich nicht darauf verlassen, etwas anderes in diesem Prozess zu tun. Sie könnten in Betracht ziehen, Ihr Test-Framework so zu gestalten, dass es mehr als einen Prozess verwendet, oder einfach akzeptieren, dass ein ausreichend schwerwiegender Testfehler den Rest der Tests verhindert. Extern zum Testframework können Sie davon ausgehen, dass Ihr Testlauf drei mögliche Ergebnisse hat: "Alle bestanden, etwas fehlgeschlagen, Tests abgestürzt". Wenn der Testlauf nicht abgeschlossen wird, behandeln Sie ihn nicht als bestanden.
Dies ist, was Standard über Dtors und Ausnahmen sagt:
15.2
(...)
Der Prozess zum Aufrufen von Destruktoren für automatische Objekte, die auf dem Pfad von einem try-Block zum Der Punkt, an dem eine Ausnahme ausgelöst wird, wird als "Stapelabwickeln" bezeichnet. Wenn ein Destruktor beim Abwickeln des Stapels aufgerufen wird beendet mit einer Ausnahme, std :: terminate wird aufgerufen (15.5.1). [ Hinweis: Destruktoren sollten also generell fangen Ausnahmen und lassen Sie sie nicht aus dem Destruktor propagieren. -Ende beachten ]
Da Sie eine sehr zweideutige Frage gestellt haben, hängt die Antwort davon ab:
Ich würde sagen, dass die Notwendigkeit, eine Ausnahme von dtor zu werfen, ein Zeichen für schlechtes Design ist. Es scheint, dass Sie etwas in Ihrem Destruktor tun, das sollte woanders gemacht werden.
Tags und Links c++ exception destructor