Ausnahmen auslösen
Das Auslösen einer Ausnahme überträgt die Kontrolle an einen Handler.
Eine Ausnahme kann über throw-Ausdrücke ausgelöst werden; die folgenden Kontexte können ebenfalls Ausnahmen auslösen:
Inhalt |
[edit] Ausnahmeobjekt
Das Auslösen einer Ausnahme initialisiert ein Objekt mit dynamischer Speicherdauer, das als *Ausnahmeobjekt* bezeichnet wird.
Wenn der Typ des Ausnahmeobjekts einer der folgenden Typen wäre, ist das Programm fehlerhaft:
- ein unvollständiger Typ
- ein abstrakter Klassentyp
- ein Zeiger auf einen unvollständigen Typ außer (möglicherweise cv-qualifiziertem) void
[edit] Konstruktion und Zerstörung von Ausnahmeobjekten
Gegeben den Typ des Ausnahmeobjekts als T
- obj sei ein lvalue vom Typ const T, muss die Kopierinitialisierung eines Objekts vom Typ
Taus obj wohlgeformt sein. - Wenn
Tein Klassentyp ist
- Der ausgewählte Konstruktor wird ODR-verwendet.
- Der Destruktor von
Twird potenziell aufgerufen.
Der Speicher für das Ausnahmeobjekt wird auf nicht spezifizierte Weise zugewiesen. Die einzige Garantie ist, dass der Speicher niemals von globalen Allokationsfunktionen zugewiesen wird.
Wenn ein Handler durch erneutes Auslösen verlassen wird, wird die Kontrolle an einen anderen Handler für dasselbe Ausnahmeobjekt übergeben. Das Ausnahmeobjekt wird in diesem Fall nicht zerstört.
|
Wenn der letzte verbleibende aktive Handler für die Ausnahme auf irgendeine Weise außer durch erneutes Auslösen verlassen wird, wird das Ausnahmeobjekt zerstört und die Implementierung kann den Speicher für das temporäre Objekt auf nicht spezifizierte Weise freigeben. Die Zerstörung erfolgt unmittelbar nach der Zerstörung des in der „Parameterliste“ des Handlers deklarierten Objekts. |
(bis C++11) |
|
Die Punkte der potenziellen Zerstörung für das Ausnahmeobjekt sind:
Unter allen Punkten der potenziellen Zerstörung für das Ausnahmeobjekt gibt es einen nicht spezifizierten letzten Punkt, an dem das Ausnahmeobjekt zerstört wird. Alle anderen Punkte finden vor diesem letzten Punkt statt. Die Implementierung kann dann den Speicher für das Ausnahmeobjekt auf nicht spezifizierte Weise freigeben. |
(seit C++11) |
[edit] throw-Ausdrücke
throw expression |
(1) | ||||||||
throw
|
(2) | ||||||||
| expression | - | der Ausdruck, der zur Konstruktion des Ausnahmeobjekts verwendet wird |
Wenn eine neue Ausnahme ausgelöst wird, wird ihr Ausnahmeobjekt wie folgt bestimmt:
- Die Array-zu-Zeiger- und Funktion-zu-Zeiger-Standardkonvertierungen werden auf expression angewendet.
- Sei ex das Konversionsergebnis.
- Der Typ des Ausnahmeobjekts wird durch Entfernen von führenden cv-Qualifizierern aus dem Typ von ex bestimmt.
- Das Ausnahmeobjekt wird aus ex kopierinitialisiert.
Wenn ein Programm versucht, eine Ausnahme erneut auszulösen, obwohl gerade keine Ausnahme behandelt wird, wird std::terminate aufgerufen. Andernfalls wird die Ausnahme mit dem vorhandenen Ausnahmeobjekt reaktiviert (es wird kein neues Ausnahmeobjekt erstellt) und die Ausnahme gilt nicht mehr als gefangen.
try { // throwing a new exception 123 throw 123; } catch (...) // catch all exceptions { // respond (partially) to exception 123 throw; // pass the exception to some other handler }
[edit] Stack unwinding
Sobald das Ausnahmeobjekt konstruiert ist, arbeitet der Kontrollfluss rückwärts (den Aufrufstapel hinauf), bis er den Anfang eines try-Blocks erreicht. Dort werden die Parameter aller zugehörigen Handler, in der Reihenfolge ihres Erscheinens, mit dem Typ des Ausnahmeobjekts verglichen, um eine Übereinstimmung zu finden. Wenn keine Übereinstimmung gefunden wird, setzt der Kontrollfluss das Auswickeln des Stacks bis zum nächsten try-Block usw. fort. Wenn eine Übereinstimmung gefunden wird, springt der Kontrollfluss zum übereinstimmenden Handler.
Während sich der Kontrollfluss den Aufrufstapel hinaufbewegt, werden Destruktoren für alle Objekte mit automatischer Speicherdauer aufgerufen, die seit dem Eintritt in den entsprechenden try-Block konstruiert, aber noch nicht zerstört wurden, und zwar in umgekehrter Reihenfolge des Abschlusses ihrer Konstruktoren. Wenn eine Ausnahme aus einem Destruktor einer lokalen Variable oder eines in einer return-Anweisung verwendeten temporären Objekts ausgelöst wird, wird auch der Destruktor für das von der Funktion zurückgegebene Objekt aufgerufen.
Wenn eine Ausnahme aus einem Konstruktor oder (selten) aus einem Destruktor eines Objekts (unabhängig von der Speicherdauer des Objekts) ausgelöst wird, werden die Destruktoren für alle vollständig konstruierten nicht-statischen, nicht-varianten Mitglieder und Basisklassen in umgekehrter Reihenfolge des Abschlusses ihrer Konstruktoren aufgerufen. Variant-Mitglieder von union-artigen Klassen werden nur beim Auswickeln aus einem Konstruktor zerstört, und wenn sich das aktive Mitglied zwischen Initialisierung und Zerstörung geändert hat, ist das Verhalten undefiniert.
|
Wenn ein delegierender Konstruktor mit einer Ausnahme verlassen wird, nachdem der nicht-delegierende Konstruktor erfolgreich abgeschlossen wurde, wird der Destruktor für dieses Objekt aufgerufen. |
(seit C++11) |
Wenn die Ausnahme aus einem Konstruktor ausgelöst wird, der durch einen new-Ausdruck aufgerufen wird, wird die übereinstimmende Deallokationsfunktion aufgerufen, falls verfügbar.
Dieser Prozess wird als *Stack unwinding* bezeichnet.
Wenn eine Funktion, die direkt durch den Stack unwinding-Mechanismus aufgerufen wird, nach der Initialisierung des Ausnahmeobjekts und vor dem Beginn des Ausnahmehandlers, mit einer Ausnahme verlassen wird, wird std::terminate aufgerufen. Solche Funktionen umfassen Destruktoren von Objekten mit automatischer Speicherdauer, deren Bereiche verlassen werden, und den Kopierkonstruktor des Ausnahmeobjekts, der aufgerufen wird (wenn nicht eliminiert), um Catch-by-Value-Argumente zu initialisieren.
Wenn eine Ausnahme ausgelöst und nicht abgefangen wird, einschließlich Ausnahmen, die die Startfunktion von std::thread, die main-Funktion und der Konstruktor oder Destruktor von statischen oder thread-lokalen Objekten verlassen, wird std::terminate aufgerufen. Ob für nicht abgefangene Ausnahmen ein Stack unwinding stattfindet, ist implementierungsabhängig.
[edit] Anmerkungen
Beim erneuten Auslösen von Ausnahmen muss die zweite Form verwendet werden, um Objekt-Slicing in dem (typischen) Fall zu vermeiden, in dem Ausnahmeobjekte Vererbung verwenden.
try { std::string("abc").substr(10); // throws std::out_of_range } catch (const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // copy-initializes a new exception object of type std::exception throw; // rethrows the exception object of type std::out_of_range }
Der throw-Ausdruck wird als prvalue-Ausdruck vom Typ void klassifiziert. Wie jeder andere Ausdruck kann er ein Unterausdruck in einem anderen Ausdruck sein, am gebräuchlichsten im bedingten Operator.
double f(double d) { return d > 1e7 ? throw std::overflow_error("too big") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
[edit] Schlüsselwörter
[edit] Beispiel
#include <iostream> #include <stdexcept> struct A { int n; A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; } ~A() { std::cout << "A(" << n << ") destroyed\n"; } }; int foo() { throw std::runtime_error("error"); } struct B { A a1, a2, a3; B() try : a1(1), a2(foo()), a3(3) { std::cout << "B constructed successfully\n"; } catch(...) { std::cout << "B::B() exiting with exception\n"; } ~B() { std::cout << "B destroyed\n"; } }; struct C : A, B { C() try { std::cout << "C::C() completed successfully\n"; } catch(...) { std::cout << "C::C() exiting with exception\n"; } ~C() { std::cout << "C destroyed\n"; } }; int main () try { // creates the A base subobject // creates the a1 member of B // fails to create the a2 member of B // unwinding destroys the a1 member of B // unwinding destroys the A base subobject C c; } catch (const std::exception& e) { std::cout << "main() failed to create C with: " << e.what(); }
Ausgabe
A(0) constructed successfully A(1) constructed successfully A(1) destroyed B::B() exiting with exception A(0) destroyed C::C() exiting with exception main() failed to create C with: error
[edit] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 499 | C++98 | Ein Array mit unbekannter Grenze konnte nicht geworfen werden, da sein Typ unvollständig ist, aber ein Ausnahmeobjekt kann problemlos aus dem zerfallenen Zeiger erstellt werden. |
Anwenden der Typvollständigkeits- Anforderung auf das Ausnahmeobjekt stattdessen. |
| CWG 668 | C++98 | std::terminate wurde nicht aufgerufen, wenn eine Ausnahme ausgelöst wird aus dem Destruktor eines lokalen nicht-automatischen Objekts. |
Aufruf von std::terminate. in diesem Fall |
| CWG 1863 | C++11 | Der Kopierkonstruktor war für nur-verschiebbare Ausnahmeobjekte beim Werfen nicht erforderlich, aber das Kopieren wurde später erlaubt. |
Kopierkonstruktor erforderlich. |
| CWG 1866 | C++98 | Variant-Mitglieder wurden beim Stack unwinding aus einem Konstruktor geleakt. | Variant-Mitglieder zerstört. |
| CWG 2176 | C++98 | Auswurf aus dem Destruktor einer lokalen Variable könnte den Rückgabewertdestruktor überspringen. |
Funktionsrückgabewert zur Abarbeitung hinzugefügt. |
| CWG 2699 | C++98 | throw "EX" würde tatsächlich char* statt const char* werfen. | korrigiert |
| CWG 2711 | C++98 | Die Quelle der Kopierinitialisierung von dem Ausnahmeobjekt war nicht spezifiziert. |
Kopierinitialisiert aus expression. |
| CWG 2775 | C++98 | Die Anforderung der Kopierinitialisierung des Ausnahmeobjekts war unklar. | wurde klargestellt |
| CWG 2854 | C++98 | Die Speicherdauer von Ausnahmeobjekten war unklar. | wurde klargestellt |
| P1825R0 | C++11 | Implizites Verschieben aus Parametern war in throw verboten. |
erlaubt |
[edit] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 7.6.18 Throwing an exception [expr.throw]
- 14.2 Throwing an exception [except.throw]
- C++20 Standard (ISO/IEC 14882:2020)
- 7.6.18 Throwing an exception [expr.throw]
- 14.2 Throwing an exception [except.throw]
- C++17 Standard (ISO/IEC 14882:2017)
- 8.17 Throwing an exception [expr.throw]
- 18.1 Throwing an exception [except.throw]
- C++14 Standard (ISO/IEC 14882:2014)
- 15.1 Throwing an exception [except.throw]
- C++11 Standard (ISO/IEC 14882:2011)
- 15.1 Throwing an exception [except.throw]
- C++03-Standard (ISO/IEC 14882:2003)
- 15.1 Throwing an exception [except.throw]
- C++98 Standard (ISO/IEC 14882:1998)
- 15.1 Throwing an exception [except.throw]
[edit] Siehe auch
| (bis C++17) |