Condividi tramite


Eccezioni e rimozione dallo stack in C++

Nel meccanismo di eccezioni di C++, il controllo si sposta dall'istruzione throw alla prima istruzione catch che può gestire il tipo generato. Quando viene raggiunta l'istruzione catch, tutte le variabili automatiche incluse nell'ambito tra le istruzioni throw e catch vengono eliminate definitivamente in un processo noto come rimozione dello stack. Nella rimozione dello stack, l'esecuzione procede come segue:

  1. Il controllo raggiunge l'istruzione in base alla try normale esecuzione sequenziale. Viene eseguita la sezione sorvegliata nel try blocco.

  2. Se non viene generata alcuna eccezione durante l'esecuzione della sezione sorvegliata, le catch clausole che seguono il try blocco non vengono eseguite. L'esecuzione continua con l'istruzione dopo l'ultima catch clausola che segue il blocco associato try .

  3. Se viene generata un'eccezione durante l'esecuzione della sezione sorvegliata o in qualsiasi routine che la sezione sorvegliata chiama direttamente o indirettamente, viene creato un oggetto eccezione dall'oggetto creato dall'operando throw . Ciò implica che potrebbe essere coinvolto un costruttore di copia. A questo punto, il compilatore cerca una catch clausola in un contesto di esecuzione superiore in grado di gestire un'eccezione del tipo generato o per un catch gestore in grado di gestire qualsiasi tipo di eccezione. I catch gestori vengono esaminati in ordine di aspetto dopo il try blocco. Se non viene trovato alcun gestore appropriato, viene esaminato il successivo blocco di inclusione try dinamica. Questo processo continua fino a quando non viene esaminato il blocco di inclusione try più esterno.

  4. Se anche in questo modo non viene individuato alcun gestore appropriato o se si verifica un'eccezione durante il processo di rimozione, ma prima che il gestore ottenga il controllo, la funzione di runtime predefinita terminate viene chiamata. Se si verifica un'eccezione dopo la generazione dell'eccezione ma prima che la rimozione abbia inizio, viene chiamato terminate.

  5. Se viene trovato un gestore corrispondente catch e intercetta per valore, il relativo parametro formale viene inizializzato copiando l'oggetto eccezione. Se le rilevazioni sono effettuate per riferimento, il parametro viene inizializzato in modo da fare riferimento all'oggetto eccezione. In seguito all'inizializzazione del parametro formale, ha inizio il processo di rimozione dello stack. Ciò comporta la distruzione di tutti gli oggetti automatici completamente costruiti, ma non ancora distrutto, tra l'inizio del try blocco associato al catch gestore e il sito di generazione dell'eccezione. La distruzione ha luogo in ordine inverso rispetto alla costruzione. Il catch gestore viene eseguito e il programma riprende l'esecuzione dopo l'ultimo gestore, ovvero alla prima istruzione o costrutto che non è un catch gestore. Il controllo può immettere un catch gestore solo tramite un'eccezione generata, mai tramite un'istruzione goto o un'etichetta case in un'istruzione switch .

Esempio di rimozione dello stack

L'esempio seguente illustra le modalità di rimozione dello stack quando viene generata un'eccezione. L'esecuzione nel thread passa dall'istruzione throw in C all'istruzione catch in main e rimuove tutte le funzioni che incontra sul suo percorso. Si noti l'ordine in cui gli oggetti Dummy vengono creati e poi distrutti quando diventano esterni all'ambito. Si noti, inoltre, che nessuna funzione è completa, ad eccezione di main, che contiene l'istruzione catch. La funzione A non viene mai restituita dalla relativa chiamata a B() e B non viene mai restituita dalla relativa chiamata a C(). Si noti che, se dalla definizione del puntatore Dummy si rimuovono il commento e l'istruzione di eliminazione corrispondente e, successivamente, si esegue il programma, il puntatore non viene mai eliminato. Questo indica ciò che può verificarsi quando le funzioni non forniscono una garanzia di eccezione. Per altre informazioni, vedere Procedura: Progettare per le eccezioni. Se si inserisce un commento al di fuori dell'istruzione catch, è possibile osservare ciò che si verifica quando un programma termina a causa di un'eccezione non gestita.

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

*/