Поделиться через


Исключения и освобождение стека в C++

В механизме исключений C++ элемент управления перемещается из оператора throw в первый оператор catch, который может обработать выданный тип. При достижении оператора catch все автоматические переменные, находящиеся в области между операторами throw и catch, удаляются в процессе, который называется очистка стека. При очистке стека выполнение продолжается следующим образом.

  1. Элемент управления достигает оператора try в процессе нормального последовательного выполнения. Выполняется защищенный раздел в блоке try.

  2. Если во время выполнения защищенного раздела исключение не создается, предложения catch, следующие за блоком try, не выполняются. Выполнение продолжается с оператора, расположенного после последнего предложения catch, следующего за связанным блоком try.

  3. Если исключение создается во время выполнения защищенного раздела или во время выполнения любой подпрограммы, прямо или косвенно вызываемой защищенным разделом, объект исключения создается из объекта, созданного операндом throw. (Это означает, что может быть задействован конструктор копии.) На этом этапе компилятор выполняет поиск предложения catch на более высоком уровне контекста выполнения, которое может обработать исключение данного типа, или обработчика catch, который может обработать любой тип исключения. Обработчики catch проверяются в порядке их отображения после блока try. Если соответствующий обработчик не найден, проверяется следующий динамический внешний блок try. Этот процесс будет продолжаться до тех пор, пока не будет проверен последний внешний блок try.

  4. Если соответствующий обработчик по-прежнему не найден или исключение возникает во время процесса очистки до получения элемента управления обработчиком, вызывается предопределенная функция времени выполнения terminate. Если исключение возникает после создания исключения, но до начала процесса очистки, вызывается функция terminate.

  5. Если соответствующий обработчик catch найден и он выполняет перехват по значению, его формальный параметр инициализируется путем копирования объекта исключения. Если обработчик выполняет перехват по ссылке, параметр инициализируется для ссылки на объект исключения. После инициализации формального параметра начинается процесс очистки стека. При этом удаляются все автоматически создаваемые объекты, которые были полностью созданы, но не удалены в области между началом блока try, связанного с обработчиком catch, и местом возникновения исключения. Удаление происходит в порядке, обратном созданию. Выполняется обработчик catch, и выполнение программы продолжается после последнего обработчика, то есть с первого оператора или конструкции, которые не являются обработчиком catch. Элемент управления может добавить обработчик catch только с помощью созданного исключения, но не с помощью оператора goto или метки case в операторе switch.

Пример очистки стека

В следующем примере показано, как очистить стек при создании исключения. Выполнение потока переходит от оператора throw в C к оператору catch в main, и при этом удаляются все функции. Обратите внимание, что порядок создания и удаления объектов Dummy соответствует порядку их выхода из области видимости. Также обратите внимание, что завершается выполнение только функции main, содержащей оператор catch. Функция A никогда не возвращается после вызова B(), и B никогда не возвращается после вызова C(). Обратите внимание, что если раскомментировать определение указателя Dummy и соответствующую инструкцию DELETE, а затем запустить программу, указатель не удаляется. Это показывает, что может произойти, если функции не предоставляют гарантию исключения. Дополнительные сведения см. в разделе "Практическое руководство. Разработка исключений". Если закомментировать оператор catch, можно наблюдать за тем, что происходит при завершении выполнения программы в результате необработанного исключения.

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