Жизненный цикл брошенных исключений в C++
Рассмотрим следующий простой код C++:
void foo() {
throw(my_exception());
}
void check_exc(const my_exception &exc) {
/* Do stuff with exc */
}
void bar() {
try {
foo();
} catch(const my_exception &exc) {
check_exc(exc);
}
}
В bar
обработчик исключений, как происходит исключение, на которое ссылается exc
еще жив, видя как это было выделено в foo
стек кадров? Разве этот кадр не должен быть размотан к тому времени, когда обработчик исключений запускается, и любые значения, выделенные там, уже считаются мертвыми? Тем более, что я явно вызываю другую функцию, которая нуждается в этом стековом пространстве.
Как программист C, пытающийся изучать C++, что я здесь неправильно понимаю? Где эти различные значения на самом деле существуют в памяти, точнее?
4 ответа
Временная переменная, созданная в throw-выражении, используется для инициализации самого объекта исключения, который (чтобы процитировать стандарт) "размещен неопределенным образом". Этот объект сохраняется (по крайней мере) до тех пор, пока исключение не будет обработано, поэтому ссылка обработчика на него действительна в обработчике или любой функции, вызываемой из обработчика.
Исключения выбрасываются по значению.
То есть указанный объект копируется (возможно, нарезается, как при любой инициализации копирования) или перемещается.
Объект исключения, на который вы можете ссылаться, например, через catch
ссылочный параметр предложения, не размещается в кадре стека исходного бросающего кода, а "неуказанным образом".
В C++11 возможные способы размещения объектов исключений (внутренне в библиотеке времени выполнения) были ограничены требованием, что на объекты исключений можно ссылаться по существу с помощью интеллектуальных указателей общего владения, std::exception_ptr
,
Возможность совместного владения означает, что в C++ объект исключения может иметь гарантированное время жизни после завершения обработки исключения.
Это главным образом для поддержки передачи исключений через код C, не учитывающий исключения, и для передачи вложенной информации об исключениях.
Реализация будет варьироваться от платформы к платформе. Жизненный цикл является более сложным, чем можно себе представить, поскольку оператор throw начинает выполняться в стековом фрейме foo().
Возможно, что объект исключения получает копии и перераспределяется, или блок catch может выполняться во фрейме поверх foo, но с указателем на фрейм бара, чтобы ссылаться на переменные бара.
Он,
линия
throw(my_exception())
генерирует новый объект типа my_exception. Вы можете указать, что вы хотите (int, enums, char * или классы). Конечно, классы имеют больше смысла, так как вы можете определить для них дополнительные данные.
После этого весь стек будет очищен, и все рекурсии будут прекращены, пока он не достигнет первого блока try/catch. Исключение все еще живо там. Код в блоке catch аналогичен реализации блока if/else, только немного умнее.
веселит,