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

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

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

  2. Если исключение не возникает во время выполнения защищенного раздела, предложения, следовать за try блоком, catch не выполняются. Выполнение продолжается в инструкции после последнего 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.

*/