Исключения и освобождение стека в C++
В механизме исключений C++ элемент управления перемещается из оператора throw в первый оператор catch, который может обработать выданный тип. После достижения инструкции catch все автоматические переменные, которые находятся в области между операторами throw и catch, уничтожаются в процессе, который называется очисткой стека. При очистке стека выполнение продолжается следующим образом.
Элемент управления достигает инструкции по обычному последовательному
try
выполнению. Выполняется защищенный раздел в блокеtry
.Если исключение не возникает во время выполнения защищенного раздела, предложения, следовать за
try
блоком,catch
не выполняются. Выполнение продолжается в инструкции после последнегоcatch
предложения, следующего за связаннымtry
блоком.Если исключение создается во время выполнения защищенного раздела или в любой подпрограмме, вызываемой защищенным разделом напрямую или косвенно, создается объект исключения из объекта, созданного операндом
throw
. (Это означает, что конструктор копирования может быть вовлечен.) На этом этапе компилятор ищетcatch
предложение в более высоком контексте выполнения, которое может обрабатывать исключение создаваемого типа илиcatch
обработчика, который может обрабатывать любой тип исключения. Обработчикиcatch
проверяются в порядке их внешнего вида послеtry
блока. Если соответствующий обработчик не найден, проверяется следующий динамически заключенныйtry
блок. Этот процесс продолжается до тех пор, пока не будет рассмотрен самый внешний заключиющийtry
блок.Если соответствующий обработчик по-прежнему не найден или исключение возникает во время процесса очистки до получения элемента управления обработчиком, вызывается предопределенная функция времени выполнения
terminate
. Если исключение возникает после создания исключения, но до начала процесса очистки, вызывается функцияterminate
.Если соответствующий обработчик найден, и он перехватывается по значению, его формальный
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.
*/