Namensräume
Varianten
Aktionen

Ausnahmen auslösen

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
throw-Ausdruck
try-Block
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 
Ausnahmen
try Block
Ausnahmen auslösen
Ausnahmen behandeln
Ausnahmespezifikation
    noexcept Spezifikation (C++11)
    dynamische Spezifikation (bis C++17*)
noexcept Operator (C++11)
 

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 T aus obj wohlgeformt sein.
  • Wenn T ein Klassentyp ist

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:

  • Wenn ein aktiver Handler für die Ausnahme auf irgendeine Weise außer durch erneutes Auslösen verlassen wird, unmittelbar nach der Zerstörung des Objekts (falls vorhanden), das in der „Parameterliste“ des Handlers deklariert wurde.
  • Wenn ein Objekt vom Typ std::exception_ptr, das auf das Ausnahmeobjekt verweist, zerstört wird, bevor der Destruktor von std::exception_ptr zurückkehrt.

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)
1) Löst eine neue Ausnahme aus.
2) Löst die gerade behandelte Ausnahme erneut aus.
expression - der Ausdruck, der zur Konstruktion des Ausnahmeobjekts verwendet wird


Wenn eine neue Ausnahme ausgelöst wird, wird ihr Ausnahmeobjekt wie folgt bestimmt:

  1. Die Array-zu-Zeiger- und Funktion-zu-Zeiger-Standardkonvertierungen werden auf expression angewendet.
  2. 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

throw

[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)