Является ли исключение ссылкой ссылкой опасным?
Обратите внимание на следующее исключение: "
void some_function() {
throw std::exception("some error message");
}
int main(int argc, char **argv) {
try {
some_function();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
exit(1);
}
return 0;
}
Безопасно ли поймать исключение броска по ссылке?
Меня беспокоит то, что исключение e
на самом деле помещено в стек some_function()
. Но some_function()
только что вернулся, в результате чего e
будет разрушено. Итак, теперь e
указывает на разрушенный объект.
Является ли мое беспокойство правильным?
Каков правильный способ передачи исключения без копирования его по значению? Должен ли я бросать new std::exception()
, чтобы он помещался в динамическую память?
Ответы
Ответ 1
Это действительно безопасно - и рекомендуется - улавливать ссылку const
.
"e
фактически помещается в стек some_function()
"
Нет, это не так... фактически созданный объект создается в неопределенной области памяти, зарезервированной для использования механизмом обработки исключений:
[except.throw] 15.1/4: Память для объекта исключений выделяется неопределенным способом, за исключением случаев, указанных в 3.7.4.1. Исключение объект уничтожается после того, как либо последний оставшийся активный обработчик для исключения выходит любыми способами, кроме реверсирования, или уничтожается последний объект типа std:: exception_ptr (18.8.5), который ссылается на объект исключения, в зависимости от того, что наступит позже.
Если локальная переменная указана на throw
, она копируется там - если необходимо (оптимизатор может иметь возможность напрямую создавать ее в этой другой памяти). Вот почему...
15.1/5 Когда брошенный объект является объектом класса, конструктор, выбранный для инициализации копирования и деструктора должны быть доступны, даже если операция копирования/перемещения завершена (12.8).
Если это не щелкнуть, это может помочь представить реализацию смутно следующим образом:
// implementation support variable...
thread__local alignas(alignof(std::max_align_t))
char __exception_object[EXCEPTION_OBJECT_BUFFER_SIZE];
void some_function() {
// throw std::exception("some error message");
// IMPLEMENTATION PSEUDO-CODE:
auto&& thrown = std::exception("some error message");
// copy-initialise __exception_object...
new (&__exception_object) decltype(thrown){ thrown };
throw __type_of(thrown);
// as stack unwinds, _type_of value in register or another
// thread_local var...
}
int main(int argc, char **argv)
{
try {
some_function();
} // IMPLEMENTATION:
// if thrown __type_of for std::exception or derived...
catch (const std::exception& e) {
// IMPLEMENTATION:
// e references *(std::exception*)(&__exception_object[0]);
...
}
}
Ответ 2
Вы должны поймать по ссылке, иначе вы не могли бы получить правильный динамический тип объекта. Что касается его срока службы, стандарт гарантирует, что в [except.throw]
,
Объект исключений уничтожается после того, как либо последний оставшийся активный обработчик для исключения выходит любыми способами, кроме реверсирования, или уничтожается последний объект типа std:: exception_ptr (18.8.5), который ссылается на объект исключения, в зависимости от того, что наступит позже
Ответ 3
Захват с помощью ссылки const - это именно то, как следует исключать исключения. Объект исключения не обязательно должен находиться в стеке. Компилятор отвечает за соответствующую магию, чтобы сделать эту работу.
С другой стороны, ваш пример не может скомпилироваться, поскольку std::exception
может быть сконфигурирован по умолчанию или создан для копирования. В этом случае метод what()
возвращает указатель на пустую строку (c-style), что не особенно полезно.
Предложите, чтобы вы выбрали std::runtime_error
или std::logic_error
, если это необходимо, или полученный из него класс:
-
logic_error
, когда вызывающий абонент запросил что-то вне параметров дизайна вашей службы.
-
runtime_error
, когда вызывающий абонент запросил что-то разумное, но внешние факторы мешают вам выполнить запрос.
http://en.cppreference.com/w/cpp/error/exception
Ответ 4
Из except.throw:
Бросание копии исключения инициализирует (8.5, 12.8) временный объект, называемый объектом исключения. Временной является значением lvalue и используется для инициализировать переменную, объявленную в соответствующем обработчике (15.3). Если тип объекта исключения будет неполным или указатель на неполный тип, отличный от (возможно, cv-qualified) void программа плохо сформирована.
Это действие бросания исключения, которое копирует объект исключения в области исключений, вне любого стека. Поэтому вполне логично и целесообразно перехватывать исключение по ссылке, поскольку срок жизни объекта исключений будет распространяться до последнего возможного catch()
.