Раскручивание стека — это процесс выполнения деструкторов для всех локальных объектов, когда исключение распространяется за пределы функции. Это происходит, когда исключение выдается и не перехватывается внутри одной и той же функции. Когда это происходит, деструкторы для всех объектов с автоматическим сроком хранения, объявленным в этой функции, вызываются в порядке, обратном их объявлению, прежде чем управление будет передано обработчику (если есть) или возвращено вызывающей стороне.
Раскручивание стека обычно прозрачно для программиста и происходит автоматически. Разматывание стека обычно связано с обработкой исключений. Когда в C++ возникает исключение, в стеке вызовов функций линейно ищется обработчик исключений, и все записи перед функцией с обработчиком исключений удаляются. Если исключение не обрабатывается в том же коде, требуется раскручивание стека (там, где оно выбрасывается). Разматывание стека — это, по сути, процесс вызова деструкторов для всех автоматизированных объектов, созданных во время выполнения (всякий раз, когда возникает исключение).
Различные подходы к разматыванию стека
Существуют разные подходы к теме раскручивания стека в деструкторе.
- Один из способов — посмотреть на это с точки зрения того, что происходит, когда генерируется исключение.
- Другой способ — посмотреть на это с точки зрения того, как вызывается деструктор, когда объект выходит за пределы области видимости.
Взгляд на раскручивание стека с точки зрения выбрасываемого исключения может помочь нам понять, почему важно иметь деструктор, который может очищаться после себя.
Когда возникает исключение:
Выполнение программы переходит к ближайшему блоку catch. Но прежде чем это произойдет, все объекты, созданные в блоке try, будут уничтожены. Сюда входят любые локальные объекты, а также любые объекты, созданные путем динамического выделения памяти. Если деструкторы для этих объектов не очищаются должным образом, это может привести к утечке памяти или другим проблемам.
С точки зрения того, как называется деструктор:
Это может помочь понять важность деструктора. Деструктор вызывается всякий раз, когда объект выходит за пределы области видимости. Когда объект выходит из области видимости, вызывается его деструктор, и все ресурсы, которые он использовал, освобождаются. Если деструктор не выполняет очистку должным образом, это может привести к утечке ресурсов или другим проблемам.
Как обнаружить раскручивание стека?
В деструкторе раскручивание стека можно обнаружить, ища признаки активности очистки, такие как следующие:
- Вызов функций, освобождающих ресурсы (например, закрытие файлов или освобождение памяти)
- Регистрация сообщений
- Установка флагов
Если какое-либо из этих действий наблюдается в деструкторе, вполне вероятно, что происходит раскручивание стека.
Использование std::uncaught_exception()
Вы можете использовать функцию std::uncaught_exception(), которая возвращает true, если исключение обрабатывается в данный момент (т. е. сгенерировано, но еще не перехвачено). Таким образом, в вашем деструкторе вы должны проверить, возвращает ли std::uncaught_exception() значение true или false, и предпринять соответствующие действия.
Пример:
С++
#include <bits/stdc++.h>
using
namespace
std;
class
MyClass {
public
:
MyClass()
{
// allocate some resource
}
~MyClass()
{
if
(!std::uncaught_exception()) {
// release resource
}
};
};
Переопределение функции std::terminate()
Чтобы определить, когда происходит раскручивание стека, вы можете переопределить функцию std::terminate(). Среда выполнения вызывает эту функцию, когда не может найти подходящий обработчик исключений. Переопределяя std::terminate(), вы можете установить точку останова или записать сообщение, чтобы облегчить отладку вашей программы.
Вот пример переопределения std::terminate():
С++
void
my_terminate()
{
// Set breakpoint here or log message
std::cerr <<
"Stack unwinding detected!"
<< std::endl;
// Call original terminate function
std::terminate();
}
int
main()
{
// Install our custom terminate handler
std::set_terminate(my_terminate);
try
{
// Code that might throw an exception goes here...
}
catch
(...) {
// Exception handlers go here...
}
return
0;
}
Установив флаг в конструкторе
Установите флаг в конструкторе и проверьте этот флаг в деструкторе. Если флаг установлен, то вы знаете, что деструктор был вызван из-за исключения. Вот пример:
С++
class
MyClass {
public
:
MyClass()
: m_isUnwinding(
false
)
{
// Constructor
}
~MyClass()
{
if
(m_isUnwinding) {
// The stack is unwinding because of an
// exception
}
else
{
// No exception is being handled
}
};
};