Ausnahmen und Stapelentladung in C++

Im C++-Ausnahmemechanismus geht die Steuerung von der throw-Anweisung zur ersten catch-Anweisung, die den ausgelösten Typ behandeln kann. Wenn die Catch-Anweisung erreicht wird, werden alle automatischen Variablen, die sich im Bereich zwischen der Wurf- und Catch-Anweisung befinden, in einem Prozess zerstört, der als Stapelentspannung bezeichnet wird. Bei der Stapelentladung wird die Ausführung wie folgt fortgesetzt:

  1. Control erreicht die try Anweisung durch normale sequenzielle Ausführung. Der geschützte Abschnitt im try Block wird ausgeführt.

  2. Wenn während der Ausführung des geschützten Abschnitts keine Ausnahme ausgelöst wird, werden die Klauseln, die catch dem try Block folgen, nicht ausgeführt. Die Ausführung wird nach der letzten catch Klausel, die dem zugeordneten try Block folgt, an der Anweisung fortgesetzt.

  3. Wenn eine Ausnahme während der Ausführung des geschützten Abschnitts oder in einer Routine ausgelöst wird, die der geschützte Abschnitt entweder direkt oder indirekt aufruft, wird ein Ausnahmeobjekt aus dem Objekt erstellt, das vom throw Operanden erstellt wird. (Dies bedeutet, dass ein Kopierkonstruktor beteiligt sein kann.) An diesem Punkt sucht der Compiler in einem Kontext mit höherer Ausführung nach einer catch Klausel, die eine Ausnahme des ausgelösten Typs oder für einen catch Handler verarbeiten kann, der einen beliebigen Ausnahmetyp verarbeiten kann. Die catch Handler werden nach dem try Block in der Reihenfolge ihrer Darstellung untersucht. Wenn kein geeigneter Handler gefunden wird, wird der nächste dynamisch eingeschlossene try Block untersucht. Dieser Vorgang wird fortgesetzt, bis der äußerste umschließende try Block untersucht wird.

  4. Wenn ein entsprechender Handler weiterhin nicht gefunden wird oder wenn während des Entladungsprozesses eine Ausnahme auftritt, bevor der Handler die Steuerung übernimmt, wird die vordefinierte Laufzeitfunktion terminate aufgerufen. Wenn eine Ausnahme auftritt, nachdem die Ausnahme ausgelöst wurde, aber bevor die Abwicklung beginnt, wird terminate aufgerufen.

  5. Wenn ein übereinstimmenden catch Handler gefunden wird und nach Wert erfasst wird, wird der formale Parameter durch Kopieren des Ausnahmeobjekts initialisiert. Wenn er als Verweis abfängt, wird der Parameter so initialisiert, dass er auf das Ausnahmeobjekt verweist. Nachdem der formale Parameter initialisiert wurde, beginnt der Prozess der Stapelentladung. Dies umfasst die Zerstörung aller automatischen Objekte, die vollständig konstruiert wurden – aber noch nicht destruktiert – zwischen dem Anfang des try Blocks, der dem catch Handler zugeordnet ist, und der Auslösen-Site der Ausnahme. Beschädigung tritt in umgekehrter Reihenfolge der Konstruktion auf. Der catch Handler wird ausgeführt, und das Programm setzt die Ausführung nach dem letzten Handler fort , d. h. bei der ersten Anweisung oder dem Konstrukt, die kein catch Handler ist. Steuerelement kann nur einen catch Handler über eine ausgelöste Ausnahme eingeben, niemals durch eine goto Anweisung oder eine case Bezeichnung in einer switch Anweisung.

Beispiel für die Stapelentwickelung

Das folgende Beispiel zeigt, wie der Stapel entladen wird, wenn eine Ausnahme ausgelöst wird. Die Ausführung auf dem Thread springt von der throw-Anweisung in C auf die catch-Anweisung in main und entlädt währenddessen jede Funktionen. Beachten Sie die Reihenfolge, in der die Dummy-Objekte erstellt und dann zerstört werden, wenn sie den Gültigkeitsbereich verlassen. Beachten Sie außerdem, dass keine Funktion außer main abgeschlossen wird, die die catch-Anweisung enthält. Funktion A kehrt nie von ihrem Aufruf von B() zurück und B nie von seinem Aufruf von C(). Wenn Sie die Definition des Dummy-Zeigers und die zugehörige Löschanweisung auskommentieren und das Programm dann ausführen, sehen Sie, dass der Zeiger nie gelöscht wird. Dies zeigt, was geschehen kann, wenn Funktionen keine Ausnahmegarantie bieten. Weitere Informationen finden Sie unter How to: Design for Exceptions. Wenn Sie die catch-Anweisung auskommentieren, können Sie nachvollziehen, was geschieht, wenn ein Programm aufgrund eines Ausnahmefehlers beendet wird.

#include <string>
#include <iostream>
using namespace std;

class MyException{};
class Dummy
{
    public:
    Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
    Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
    ~Dummy(){ PrintMsg("Destroyed Dummy:"); }
    void PrintMsg(string s) { cout << s  << MyName <<  endl; }
    string MyName;
    int level;
};

void C(Dummy d, int i)
{
    cout << "Entering FunctionC" << endl;
    d.MyName = " C";
    throw MyException();

    cout << "Exiting FunctionC" << endl;
}

void B(Dummy d, int i)
{
    cout << "Entering FunctionB" << endl;
    d.MyName = "B";
    C(d, i + 1);
    cout << "Exiting FunctionB" << endl;
}

void A(Dummy d, int i)
{
    cout << "Entering FunctionA" << endl;
    d.MyName = " A" ;
  //  Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
    B(d, i + 1);
 //   delete pd;
    cout << "Exiting FunctionA" << endl;
}

int main()
{
    cout << "Entering main" << endl;
    try
    {
        Dummy d(" M");
        A(d,1);
    }
    catch (MyException& e)
    {
        cout << "Caught an exception of type: " << typeid(e).name() << endl;
    }

    cout << "Exiting main." << endl;
    char c;
    cin >> c;
}

/* Output:
    Entering main
    Created Dummy: M
    Copy created Dummy: M
    Entering FunctionA
    Copy created Dummy: A
    Entering FunctionB
    Copy created Dummy: B
    Entering FunctionC
    Destroyed Dummy: C
    Destroyed Dummy: B
    Destroyed Dummy: A
    Destroyed Dummy: M
    Caught an exception of type: class MyException
    Exiting main.

*/