Wyjątki i odwijanie stosu w języku C++

W mechanizmie wyjątków C++, kontrola zostaje przekazana z instrukcji throw do pierwszej instrukcji catch, która może obsłużyć wyrzucony typ. Po osiągnięciu instrukcji catch wszystkie zmienne automatyczne, które znajdują się w zakresie między instrukcjami throw i catch, są niszczone w procesie znanym jako odwijanie stosu. W odwijaniu stosu, wykonanie przebiega w następujący sposób:

  1. Kontrolka osiąga instrukcję try normalną sekwencyjną. Sekcja chroniona w try bloku jest wykonywana.

  2. Jeśli podczas wykonywania chronionej sekcji nie jest zgłaszany żaden wyjątek, klauzule, które następują try po bloku, catch nie są wykonywane. Wykonanie jest kontynuowane w instrukcji po ostatniej catch klauzuli, która jest zgodna ze skojarzonym try blokiem.

  3. Jeśli podczas wykonywania chronionej sekcji jest zgłaszany wyjątek lub w jakiejkolwiek procedurze wywoływanej bezpośrednio lub pośrednio przez chronioną sekcję, obiekt wyjątku jest tworzony na podstawie obiektu utworzonego throw przez operand. (Oznacza to, że może być zaangażowany konstruktor kopiujący). W tym momencie kompilator szuka catch klauzuli w wyższym kontekście wykonywania, który może obsługiwać wyjątek typu, który jest zgłaszany, lub dla catch programu obsługi, który może obsłużyć dowolny typ wyjątku. Procedury catch obsługi są badane w kolejności ich wyglądu try po bloku. Jeśli nie zostanie znaleziona odpowiednia procedura obsługi, zostanie zbadany następny dynamicznie otaczający try blok. Ten proces trwa do czasu zbadania najbardziej zewnętrznego bloku otaczającego try .

  4. Jeśli nadal nie znaleziono pasującej klauzuli obsługi lub jeśli podczas procesu odwijania wystąpi wyjątek, ale zanim klauzula obsługi przejmie kontrolę, wywoływana jest wstępnie zdefiniowana funkcja terminate. Jeśli wyjątek wystąpi po wyrzuceniu wyjątku, ale przed rozpoczęciem odwijania, wywoływane jest terminate.

  5. Jeśli zostanie znaleziona zgodna catch procedura obsługi i przechwytuje ją według wartości, jego parametr formalny jest inicjowany przez skopiowanie obiektu wyjątku. Jeżeli przechwyci kontrolę przez odwołanie, inicjowany jest parametr odwołujący się do obiektu wyjątku. Po zainicjowaniu parametru formalnego, rozpocznie się proces odwijania stosu. Obejmuje to zniszczenie wszystkich obiektów automatycznych, które zostały w pełni skonstruowane — ale jeszcze niezniszczone — między początkiem try bloku skojarzonego z catch procedurą obsługi a lokacją zgłaszania wyjątku. Destrukcja następuje w odwrotnej kolejności do konstrukcji. Procedura catch obsługi jest wykonywana, a program wznawia wykonywanie po ostatniej procedurze obsługi — czyli w pierwszej instrukcji lub konstrukcji, która nie jest procedurą catch obsługi. Kontrolka może wprowadzać procedurę catch obsługi tylko za pomocą wyjątku zgłaszanego, nigdy za pomocą goto instrukcji lub case etykiety w instrukcji switch .

Przykład odwijania stosu

W poniższym przykładzie zilustrowano, jak stos jest odwijany, gdy zostaje wyrzucony wyjątek. Wykonanie na wątku przechodzi z instrukcji throw w C do instrukcji catch w main i rozwija każdą funkcję po drodze. Należy zauważyć kolejność, w której obiekty Dummy są tworzone i następnie niszczone, gdy wykraczają poza zakres. Należy również zauważyć, że żadna funkcja nie zostaje zakończona z wyjątkiem main, która zawiera instrukcję catch. Funkcja A nigdy nie wraca z wywołania B(), a B nigdy nie wraca z wywołania C(). Jeśli usuniesz komentarz definicji wskaźnika Dummy i odpowiadającą instrukcję delete, a następnie uruchomisz program, zobaczysz, że wskaźnik nigdy nie jest usuwany. Pokazuje to, co może się zdarzyć, gdy funkcje nie zapewniają gwarancji wyjątku. Aby uzyskać więcej informacji, zobacz How to: Design for Exceptions (Instrukcje: projektowanie wyjątków). Jeśli komentarz wystąpi poza instrukcją catch, można zaobserwować, co się dzieje, gdy program zakończy wykonanie z powodu nieobsługiwanego wyjątku.

#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.

*/