Итак, чтобы поймать исключение, вы должны быть хорошо проинформированы об использовании std::optional
в реализации элемента
Нет, чтобы поймать исключение, вы должны прочитать документация для get_placement
, которая скажет вам, что она выбрасывает std::bad_optional_access
. Выбрав отправку этого исключения, функция делает передачу этого исключения частью интерфейса этой функции.
И, следовательно, она больше не зависит от Item
' Реализация, чем если бы он прямо вернул std::optional
. Вы решаете поместить его в свой интерфейс, так что вам следует смириться с последствиями.
Другими словами, если вы чувствовали, что указание std::optional
в качестве типа параметра или возвращаемого значения было неправильным, то вы должно быть то же самое по отношению к прямому испусканию bad_optional_exception
.
В конечном счете, все это восходит к одному из самых фундаментальных вопросов обработки ошибок: как далеко вы можете добраться до места возникновения ошибки прежде чем специфицированный c характер ошибки станет фактически бессмысленным или даже совершенно другим?
Допустим, вы выполняете обработку текста. У вас есть файл с каждой строкой, содержащей 3 числа с плавающей точкой. Вы обрабатываете это построчно и вставляете каждый набор из трех значений в список. И у вас есть функция, которая преобразует строки в числа с плавающей точкой, которая выдает исключение, если это преобразование не удается.
Итак, код в целом выглядит следующим образом:
for each line
split the line into a 3-element list of number strings.
for each number string
convert the string into a number.
add the number to the current element.
push the element into the list.
Хорошо, так что ... что произойдет, если ваш конвертер строки в плавающий выбрасывает? Это зависит от; что вы хотите, чтобы произошло? Это определяется тем, кто ловит это. Если вам нужно значение по умолчанию для ошибки, тогда код в самом внутреннем l oop перехватывает его и записывает значение по умолчанию в элемент.
Но, возможно, вы хотите записать, что в конкретной строке есть ошибка , затем пропустите эту строку (не добавляйте ее в список), но продолжайте обрабатывать остальную часть текста как обычно. В этом случае вы ловите исключение в первом l oop.
В этот момент значение ошибки изменилось . Было выдано сообщение об ошибке: «эта строка не содержит допустимого числа с плавающей запятой», но ваш код не справляется с этим. Фактически, код перехвата полностью потерял контекст ошибки. Он не знает, была ли первая, вторая или третья строка в тексте причиной ошибки. В лучшем случае, он знает, что это было где-то вдоль этой линии, и, возможно, исключение содержит пару указателей на диапазон плохих строк (хотя это становится все более опасным, чем дальше это исключение получает из своего источника, из-за возможности висячих указателей ).
А что, если неудачное преобразование должно означать, что всему процессу больше нельзя доверять, что созданный вами список недопустим и должен быть отброшен? Это имеет даже меньше контекста, чем предыдущий случай, а значение еще более запутанное и отдаленное. На этом этапе ошибка означает просто завершение процесса создания списка. Может быть, вы собрали запись в журнале, но это все, что вы собираетесь сделать на этом этапе.
Чем дальше вы получаете от того, где выдается исключение, тем больше теряется контекст об ошибке, и больше значение в конечном итоге отклоняется от первоначального значения ошибки. Это не просто деталь реализации; речь идет о местонахождении информации и ответе на эту информацию.
Таким образом, в основном код, близкий к источнику ошибки, перехватывает определенные c исключения с контекстным значением. Чем дальше перехватчик получает от источника ошибки, тем более вероятно, что код перехвата будет очень обобщенным c, имея дело с расплывчатым «это не работает по причинам». Вот тут-то и появляются неопределенные типы, такие как std::logic_error
.
Действительно, можно представить, что на каждом этапе процесса исключение интерпретируется по-новому (и под «переосмыслением» я имею в виду преобразование его в другой тип). через catch/throw
). Конвертер string-to-float выдает значимое исключение: не удалось преобразовать строку в float. Слой, пытающийся построить элемент из 3 строк, преобразует исключение во что-то, что имеет значение для его вызывающего: строковый индекс X искажен. И на последнем этапе исключение обобщается на: не удалось проанализировать список из-за строки Y.
Идея о том, что один тип исключения может перепрыгивать через целые библиотеки кода и разработанного намерения, и при этом сохранять его первоначальный смысл это фантазия. Исключения прекрасно работают, когда они должны проходить через нейтральный код, например, генерировать исключение из обратного вызова или какой-либо другой случай косвенного выполнения функции. В этом случае код, который спровоцировал выполнение, все еще имеет локальный контекст процесса, который спровоцировал исключение. Но чем дальше от локального контекста кто знает, что происходит, тем менее значимым становится конкретное c исключение.
Наследование от logic_error
неверно по этим причинам. Поймать bad_optional_access
в конечном итоге очень локально. После определенного момента значение этой ошибки меняется.
«logi c error» означает, что ваша программа не имеет смысла. Но необязательный параметр, который не содержит значения, не обязательно означает, что . В одном фрагменте кода может быть совершенно правильным иметь пустой необязательный аргумент, и выбрасываемое исключение - это просто то, как об этом сообщается вызывающей стороне. В другом фрагменте кода необязательный элемент может быть пустым в определенный момент как пользователь, совершивший некоторую предыдущую ошибку при использовании API. Одной из них является ошибка logi c, а другой - нет.
В конечном счете, правильное, что нужно сделать, это убедиться, что все ваши API классов генерируют исключения, которые значимы для звонящего. И не ясно, что bad_optional_access
означает для абонента get_placement
.