Throwing exceptions
Das Werfen einer Exception übergibt die Kontrolle an einen Handler .
Eine Ausnahme kann aus throw expressions geworfen werden, die folgenden Kontexte können ebenfalls Ausnahmen werfen:
Inhaltsverzeichnis |
Exception-Objekt
Das Werfen einer Exception initialisiert ein Objekt mit dynamischer storage duration , genannt das exception object .
Wenn der Typ des Ausnahmeobjekts einer der folgenden Typen wäre, ist das Programm fehlerhaft:
- ein incomplete type
- ein abstract class type
- ein Zeiger auf einen incomplete type außer (möglicherweise cv-qualifiziertem) void
Konstruktion und Destruktion von Exception-Objekten
Angesichts des Typs des Ausnahmeobjekts als
T
:
-
Sei
obj
ein L-Wert vom Typ
const
T
, dann muss die
Copy-Initialisierung
eines Objekts vom Typ
Taus obj wohlgeformt sein. -
Falls
Tein Klassentyp ist:
-
- Der ausgewählte Konstruktor wird ODR-used .
-
Der
Destruktor
von
Twird potentially invoked .
Der Speicher für das Ausnahmeobjekt wird auf nicht näher spezifizierte Weise allokiert. Die einzige Garantie ist, dass der Speicher niemals durch globale Allokationsfunktionen bereitgestellt wird.
Wenn ein Handler durch erneutes Werfen beendet wird, wird die Steuerung 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 eine andere Weise als durch erneutes Werfen beendet 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 im "Parameterlist" des Handlers deklarierten Objekts. |
(bis C++11) |
|
Die möglichen Zeitpunkte für die Zerstörung des Ausnahmeobjekts sind:
Unter allen möglichen Zeitpunkten für die Zerstörung des Ausnahmeobjekts gibt es einen nicht spezifizierten letzten Zeitpunkt, an dem das Ausnahmeobjekt zerstört wird. Alle anderen Zeitpunkte happen before diesem letzten Zeitpunkt. Die Implementierung kann dann den Speicher für das Ausnahmeobjekt auf nicht spezifizierte Weise freigeben. |
(seit C++11) |
throw Ausdrücke
throw
Ausdruck
|
(1) | ||||||||
throw
|
(2) | ||||||||
| expression | - | der Ausdruck, der zur Konstruktion des Exception-Objekts verwendet wird |
Wenn eine neue Exception ausgelöst wird, wird ihr Exception-Objekt wie folgt bestimmt:
- Die Array-zu-Zeiger und Funktion-zu-Zeiger Standardkonvertierungen werden auf den Ausdruck angewendet.
- Sei ex das Konvertierungsergebnis:
-
-
- Der Typ des Ausnahmeobjekts wird durch Entfernen aller obersten cv-Qualifizierer vom Typ von ex bestimmt.
- Das Ausnahmeobjekt wird copy-initialized von ex .
-
Wenn ein Programm versucht, eine Ausnahme erneut auszulösen, wenn gegenwärtig keine Ausnahme behandelt wird, std::terminate wird aufgerufen. Andernfalls wird die Ausnahme mit dem bestehenden Ausnahmeobjekt reaktiviert (es wird kein neues Ausnahmeobjekt erstellt), und die Ausnahme wird nicht länger als abgefangen betrachtet.
try { // Auslösen einer neuen Ausnahme 123 throw 123; } catch (...) // alle Ausnahmen abfangen { // (Teilweise) auf Ausnahme 123 reagieren throw; // Ausnahme an anderen Handler weiterleiten }
Stack-Abbau
Sobald das Ausnahmeobjekt konstruiert ist, arbeitet der Kontrollfluss rückwärts (den Aufrufstapel hinauf), bis er den Beginn eines try Blocks erreicht, an welchem Punkt die Parameter aller zugehörigen Handler der Reihe nach mit dem Typ des Ausnahmeobjekts verglichen werden, um eine Übereinstimmung zu finden. Wenn keine Übereinstimmung gefunden wird, setzt der Kontrollfluss das Abwickeln des Stapels fort bis zum nächsten try Block, und so weiter. Wenn eine Übereinstimmung gefunden wird, springt der Kontrollfluss zum passenden Handler.
Während der Kontrollfluss den Aufrufstapel hinaufsteigt, werden Destruktoren für alle Objekte mit automatischer Speicherdauer aufgerufen, die konstruiert, aber noch nicht zerstört wurden, seit der entsprechende try -Block betreten wurde, in umgekehrter Reihenfolge der Vollendung ihrer Konstruktoren. Wenn eine Exception aus einem Destruktor einer lokalen Variable oder eines temporären Objekts geworfen wird, das in einer return -Anweisung verwendet wird, wird auch der Destruktor für das aus der Funktion zurückgegebene Objekt aufgerufen.
Wenn eine Ausnahme aus einem Konstruktor oder (selten) aus einem Destruktor eines Objekts geworfen wird (unabhängig von der Speicherdauer des Objekts), werden Destruktoren für alle vollständig konstruierten nicht-statischen nicht-varianten Member und Basisklassen in umgekehrter Reihenfolge der Fertigstellung ihrer Konstruktoren aufgerufen. Variante Member von union-like classes werden nur im Falle des Unwindings aus dem Konstruktor zerstört, und wenn sich der aktive Member zwischen Initialisierung und Zerstörung ändert, ist das Verhalten undefiniert.
|
Wenn ein delegierender Konstruktor mit einer Exception beendet wird, nachdem der nicht-delegierende Konstruktor erfolgreich abgeschlossen wurde, wird der Destruktor für dieses Objekt aufgerufen. |
(since C++11) |
Wenn die Ausnahme von einem Konstruktor ausgelöst wird, der durch einen new-expression aufgerufen wird, wird die passende deallocation function aufgerufen, sofern verfügbar.
Dieser Vorgang wird als stack unwinding bezeichnet.
Wenn irgendeine Funktion, die direkt vom Stack-Unwinding-Mechanismus aufgerufen wird, nach der Initialisierung des Exception-Objekts und vor dem Beginn des Exception-Handlers mit einer Exception beendet wird, std::terminate wird aufgerufen. Solche Funktionen umfassen Destruktoren von Objekten mit automatischer Speicherdauer, deren Gültigkeitsbereiche verlassen werden, und den Kopierkonstruktor des Exception-Objekts, der (falls nicht elided ) aufgerufen wird, um Catch-by-Value-Argumente zu initialisieren.
Wenn eine Exception ausgelöst wird und nicht abgefangen wird, einschließlich Exceptions, die aus der initialen Funktion von std::thread , der main-Funktion und dem Konstruktor oder Destruktor von statischen oder thread-lokalen Objekten entweichen, wird std::terminate aufgerufen. Es ist implementierungsdefiniert, ob für nicht abgefangene Exceptions ein Stack Unwinding stattfindet.
Hinweise
Beim erneuten Auslösen von Ausnahmen muss die zweite Form verwendet werden, um Objektslicing im (typischen) Fall zu vermeiden, in dem Ausnahmeobjekte Vererbung verwenden:
try { std::string("abc").substr(10); // wirft std::out_of_range } catch (const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // initialisiert ein neues Exception-Objekt vom Typ std::exception durch Kopie throw; // wirft das Exception-Objekt vom Typ std::out_of_range erneut }
Der throw -Ausdruck wird klassifiziert als Prvalue-Ausdruck vom Typ void . Wie jeder andere Ausdruck kann er ein Teilausdruck in einem anderen Ausdruck sein, am häufigsten im Bedingungsoperator :
double f(double d) { return d > 1e7 ? throw std::overflow_error("zu groß") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
`-Tags bleiben unverändert, wie angefordert.
Schlüsselwörter
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
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 vom zerfallenen Zeiger ohne Probleme erstellt werden |
Wende die Typ-Vervollständigungs-
anforderung stattdessen auf das Ausnahmeobjekt an |
| CWG 668 | C++98 |
std::terminate
wurde nicht aufgerufen, wenn eine Ausnahme
vom Destruktor eines lokalen nicht-automatischen Objekts geworfen wird |
Rufe
std::terminate
in diesem Fall auf |
| CWG 1863 | C++11 |
Copy-Konstruktor war nicht erforderlich für move-only
Ausnahmeobjekte beim Werfen, aber Kopieren später erlaubt |
Copy-Konstruktor erforderlich |
| CWG 1866 | C++98 | Variant-Mitglieder wurden beim Stack-Unwinding aus Konstruktor verworfen | Variant-Mitglieder zerstört |
| CWG 2176 | C++98 |
Wurf aus einem lokalen Variablen-Destruktor
könnte Rückgabewert-Destruktor überspringen |
Funktionsrückgabewert
zum Unwinding hinzugefügt |
| CWG 2699 | C++98 | throw "EX" würde tatsächlich char * werfen statt const char * | korrigiert |
| CWG 2711 | C++98 |
Die Quelle der Copy-Initialisierung des
Ausnahmeobjekts war nicht spezifiziert |
Copy-initialisiert
von expression |
| CWG 2775 | C++98 | Die Anforderung an die Copy-Initialisierung des Ausnahmeobjekts war unklar | Klar gestellt |
| CWG 2854 | C++98 | Die Speicherdauer von Ausnahmeobjekten war unklar | Klar gestellt |
| P1825R0 | C++11 |
Implizites Move von Parametern war verboten in
throw
|
erlaubt |
Referenzen
- C++23-Standard (ISO/IEC 14882:2024):
-
- 7.6.18 Auslösen einer Ausnahme [expr.throw]
-
- 14.2 Auslösen einer Ausnahme [except.throw]
- C++20-Standard (ISO/IEC 14882:2020):
-
- 7.6.18 Auslösen einer Ausnahme [expr.throw]
-
- 14.2 Auslösen einer Ausnahme [except.throw]
- C++17-Standard (ISO/IEC 14882:2017):
-
- 8.17 Auslösen einer Exception [expr.throw]
-
- 18.1 Auslösen einer Exception [except.throw]
- C++14-Standard (ISO/IEC 14882:2014):
-
- 15.1 Auslösen einer Ausnahme [except.throw]
- C++11-Standard (ISO/IEC 14882:2011):
-
- 15.1 Auslösen einer Ausnahme [except.throw]
- C++03-Standard (ISO/IEC 14882:2003):
-
- 15.1 Auslösen einer Ausnahme [except.throw]
- C++98 Standard (ISO/IEC 14882:1998):
-
- 15.1 Auslösen einer Ausnahme [except.throw]
Siehe auch
| (bis C++17) |