Ausnahmen behandeln
Eine Ausnahme kann von einem Handler behandelt werden.
Inhalt |
[bearbeiten] Handler
catch ( attr (optional) type-specifier-seq declarator ) compound-statement |
(1) | ||||||||
catch ( attr (optional) type-specifier-seq abstract-declarator (optional) ) compound-statement |
(2) | ||||||||
catch ( ... ) compound-statement |
(3) | ||||||||
| attr | - | (seit C++11) eine beliebige Anzahl von Attributen, die auf den Parameter angewendet werden |
| type-specifier-seq | - | Teil einer formalen Parameterdeklaration, wie in einer Parameterliste einer Funktion |
| Deklarator | - | Teil einer Parameterdeklaration, wie in einer Parameterliste einer Funktion |
| abstract-declarator | - | Teil einer unbenannten Parameterdeklaration, wie in einer Parameterliste einer Funktion |
| compound-statement | - | eine zusammengesetzte Anweisung |
Die Parameterdeklaration in einem Handler beschreibt den oder die Typen von Ausnahmen, die dazu führen können, dass dieser Handler betreten wird.
Wenn der Parameter mit einem der folgenden Typen deklariert ist, ist das Programm schlecht geformt
| (seit C++11) |
- ein Zeiger auf einen unvollständigen Typ, außer (möglicherweise cv-qualifiziertem) void
- eine lvalue-Referenz auf einen unvollständigen Typ
Wenn der Parameter vom Typ „Array von T“ oder vom Funktionstyp T deklariert ist, wird der Typ zu „Zeiger auf T“ angepasst.
Ein Handler mit dem Parametertyp T kann als „ein Handler vom Typ T“ abgekürzt werden.
[bearbeiten] Übereinstimmende Ausnahmen
Jeder try-Block ist mit einer Anzahl von Handlern assoziiert; diese Handler bilden eine Handlersequenz. Wenn eine Ausnahme aus einem try-Block geworfen wird, werden die Handler in der Reihenfolge ihres Erscheinens versucht, um die Ausnahme abzugleichen.
Ein Handler ist eine Übereinstimmung für ein Ausnahmeobjekt vom Typ E, wenn eine der folgenden Bedingungen erfüllt ist
- Der Handler ist vom Typ „möglicherweise cv-qualifiziertes
T“ oder „lvalue-Referenz auf möglicherweise cv-qualifiziertesT“, und eine der folgenden Bedingungen ist erfüllt
-
EundTsind derselbe Typ (oberste cv-Qualifizierer ignoriert). -
Tist eine eindeutige öffentliche Basisklasse vonE.
-
- Der Handler ist vom Typ „möglicherweise cv-qualifiziertes
T“ oder const T&, wobeiTein Zeiger- oder Zeiger-auf-Mitglied-Typ ist, und eine der folgenden Bedingungen ist erfüllt
-
Eist ein Zeiger- oder Zeiger-auf-Mitglied-Typ, der durch mindestens eine der folgenden Konvertierungen inTkonvertiert werden kann
- Eine Standard-Zeigerkonvertierung, die keine Konvertierungen zu Zeigern auf private, protected oder mehrdeutige Klassen beinhaltet.
-
| (seit C++17) |
|
(seit C++11) |
Der catch (...)-Handler gleicht Ausnahmen beliebigen Typs ab. Falls vorhanden, kann er nur der letzte Handler in einer Handlersequenz sein. Dieser Handler kann verwendet werden, um sicherzustellen, dass keine unbehandelten Ausnahmen aus einer Funktion entkommen können, die eine Nothrow-Ausnahmegarantie bietet.
try { f(); } catch (const std::overflow_error& e) {} // this executes if f() throws std::overflow_error (same type rule) catch (const std::runtime_error& e) {} // this executes if f() throws std::underflow_error (base class rule) catch (const std::exception& e) {} // this executes if f() throws std::logic_error (base class rule) catch (...) {} // this executes if f() throws std::string or int or any other unrelated type
Wenn keine Übereinstimmung unter den Handlern für einen try-Block gefunden wird, wird die Suche nach einem übereinstimmenden Handler in einem dynamisch umschließenden try-Block fortgesetzt desselben Threads(seit C++11).
Wenn kein übereinstimmender Handler gefunden wird, wird std::terminate aufgerufen; ob der Stack vor diesem Aufruf von std::terminate entrollt wird, ist implementierungsabhängig.
[bearbeiten] Ausnahmen behandeln
Wenn eine Ausnahme geworfen wird, wird die Kontrolle an den nächstgelegenen Handler mit einem übereinstimmenden Typ übertragen; „nächstgelegen“ bedeutet der Handler, dessen zusammengesetzte Anweisung oder Member-Initialisierungsliste (falls vorhanden) nach dem Schlüsselwort try vom Kontrollfaden am kürzesten betreten und noch nicht verlassen wurde.
[bearbeiten] Initialisierung des Handler-Parameters
Der Parameter, der in der Parameterliste deklariert ist (falls vorhanden), vom Typ „möglicherweise cv-qualifiziertes T“ oder „lvalue-Referenz auf möglicherweise cv-qualifiziertes T“, wird vom Ausnahmeobjekt vom Typ E wie folgt initialisiert
- Wenn
Teine Basisklasse vonEist, wird der Parameter von einem lvalue vom TypT, der die entsprechende Basisklassen-Subobjekt des Ausnahmeobjekts bezeichnet, kopierinitialisiert. - Andernfalls wird der Parameter von einem lvalue vom Typ
E, der das Ausnahmeobjekt bezeichnet, kopierinitialisiert.
Die Lebensdauer des Parameters endet, wenn der Handler verlassen wird, nach der Zerstörung aller Objekte mit automatischer Speicherdauer, die innerhalb des Handlers initialisiert wurden.
Wenn der Parameter als Objekt deklariert ist, haben Änderungen an diesem Objekt keine Auswirkungen auf das Ausnahmeobjekt.
Wenn der Parameter als Referenz auf ein Objekt deklariert ist, sind Änderungen am referenzierten Objekt Änderungen am Ausnahmeobjekt und haben Auswirkungen, falls dieses Objekt erneut geworfen wird.
[bearbeiten] Aktivierung des Handlers
Ein Handler gilt als aktiv, wenn die Initialisierung für den Parameter (falls vorhanden) des Handlers abgeschlossen ist.
Außerdem gilt ein impliziter Handler als aktiv, wenn std::terminate aufgrund eines `throw` betreten wird.
Ein Handler gilt nicht mehr als aktiv, wenn der Handler verlassen wird.
Die Ausnahme mit dem zuletzt aktivierten Handler, der noch aktiv ist, wird als aktuell behandelte Ausnahme bezeichnet. Eine solche Ausnahme kann erneut geworfen werden.
[bearbeiten] Kontrollfluss
Die compound-statement eines Handlers ist eine kontrollflussbeschränkte Anweisung
void f() { goto label; // error try { goto label; // error } catch (...) { goto label: // OK label: ; } }
[bearbeiten] Hinweise
Stack unwinding tritt auf, während die Kontrolle an einen Handler übertragen wird. Wenn ein Handler aktiv wird, ist das Stack unwinding bereits abgeschlossen.
Die von dem `throw`-Ausdruck throw 0 geworfene Ausnahme stimmt nicht mit einem Handler vom Zeiger- oder Zeiger-auf-Mitglied-Typ überein.
|
(seit C++11) |
Ausnahmeobjekte können niemals Array- oder Funktionstypen haben, daher ist ein Handler, der eine Referenz auf einen Array- oder Funktionstyp ist, niemals eine Übereinstimmung für irgendein Ausnahmeobjekt.
Es ist möglich, Handler zu schreiben, die niemals ausgeführt werden können, z. B. indem ein Handler für eine finale abgeleitete Klasse nach einem Handler für eine entsprechende eindeutige öffentliche Basisklasse platziert wird
try { f(); } catch (const std::exception& e) {} // will be executed if f() throws std::runtime_error catch (const std::runtime_error& e) {} // dead code!
Viele Implementierungen erweitern übermäßig die Auflösung von CWG-Problem 388 auf Handler für Referenzen auf nicht-const Zeigertypen
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // should pass, but fails on MSVC and Clang }
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
Das folgende Beispiel demonstriert verschiedene Anwendungsfälle von Handlern
#include <iostream> #include <vector> int main() { try { std::cout << "Throwing an integer exception...\n"; throw 42; } catch (int i) { std::cout << " the integer exception was caught, with value: " << i << '\n'; } try { std::cout << "Creating a vector of size 5... \n"; std::vector<int> v(5); std::cout << "Accessing the 11th element of the vector...\n"; std::cout << v.at(10); // vector::at() throws std::out_of_range } catch (const std::exception& e) // caught by reference to base { std::cout << " a standard exception was caught, with message: '" << e.what() << "'\n"; } }
Mögliche Ausgabe
Throwing an integer exception... the integer exception was caught, with value: 42 Creating a vector of size 5... Accessing the 11th element of the vector... a standard exception was caught, with message: 'out_of_range'
[bearbeiten] 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 98 | C++98 | Eine switch-Anweisung kann die Kontrolle in einen Handler übertragen | verboten |
| CWG 210 | C++98 | throw-Ausdrücke wurden mit den Handlern abgeglichen | Ausnahmeobjekte sind mit den Handlern abgeglichen |
| CWG 388 | C++98 | Ein Ausnahmeobjekt vom Typ Zeiger oder Zeiger auf Mitglied konnte nicht durch eine const-Referenz auf einen anderen Typ abgeglichen werden |
wurde abgleichbar gemacht wenn konvertierbar |
| CWG 1166 | C++98 | Das Verhalten war undefiniert, wenn ein Handler, dessen Typ eine Referenz auf einen abstrakten Klassentyp ist, abgeglichen wurde |
abstrakte Klassentypen sind nicht für Handler erlaubt |
| CWG 1769 | C++98 | Wenn der Typ des Handlers eine Basisklasse des Typs des Ausnahmeobjekts ist, könnte ein konvertierender Konstruktor für die Initialisierung des Handler-Parameters verwendet werden |
Der Parameter wird kopierinitialisiert aus der entsprechenden Basisklasse Subobjekt des Ausnahmeobjekts |
| CWG 2093 | C++98 | Ein Ausnahmeobjekt vom Typ Zeiger auf Objekt konnte keinen Handler vom Typ Zeiger auf Objekt durch eine Qualifizierungskonvertierung abgleichen |
erlaubt |
[bearbeiten] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 14.4 Handling an exception [except.handle]
- C++20 Standard (ISO/IEC 14882:2020)
- 14.4 Handling an exception [except.handle]
- C++17 Standard (ISO/IEC 14882:2017)
- 18.3 Handling an exception [except.handle]
- C++14 Standard (ISO/IEC 14882:2014)
- 15.3 Handling an exception [except.handle]
- C++11 Standard (ISO/IEC 14882:2011)
- 15.3 Handling an exception [except.handle]
- C++03-Standard (ISO/IEC 14882:2003)
- 15.3 Handling an exception [except.handle]
- C++98 Standard (ISO/IEC 14882:1998)
- 15.3 Handling an exception [except.handle]