До исключений в C были setjmp
и longjmp
, которые могли бы использоваться для аналогичной развертки фрейма стека.
Тогда той же конструкции было дано имя: «Исключение». И большинство ответов полагаются на значение этого имени, чтобы спорить о его использовании, утверждая, что исключения предназначены для использования в исключительных условиях. Это никогда не было целью в оригинале longjmp
. Были ситуации, когда вам нужно было разорвать поток управления на множество кадров стека.
Исключения немного более общие в том смысле, что вы можете использовать их в одном и том же фрейме стека. Это приводит к аналогиям с goto
, которые я считаю неправильными. Gotos - это тесно связанная пара (как и setjmp
и longjmp
). Исключения следуют из слабосвязанной публикации / подписки, которая намного чище! Поэтому их использование в одном и том же стековом кадре - это совсем не то же самое, что goto
s.
Третий источник путаницы связан с тем, являются ли они проверенными или непроверенными исключениями. Конечно, непроверенные исключения кажутся особенно ужасными в использовании для управления потоком и, возможно, для множества других вещей.
Проверенные исключения, тем не менее, хороши для управления потоком, как только вы преодолеете все викторианские зависания и немного поживете.
Мое любимое использование - это последовательность throw new Success()
в длинном фрагменте кода, который пробует одно за другим, пока не найдет то, что ищет. Каждая вещь - каждая часть логики - может иметь произвольное вложение, поэтому break
отсутствуют, как и любые тесты условий. Шаблон if-else
является хрупким. Если я отредактирую else
или испорчу синтаксис каким-либо другим способом, то это будет волосатая ошибка.
Использование throw new Success()
линеаризует поток кода. Я использую локально определенные Success
классы - проверено, конечно, - так что, если я забуду его перехватить, код не скомпилируется. И я не поймаю Success
es другого метода.
Иногда мой код проверяет одну вещь за другой и только успешно, если все в порядке. В этом случае у меня аналогичная линеаризация с использованием throw new Failure()
.
Использование отдельной функции портит естественный уровень компартментализации. Таким образом, решение return
не является оптимальным. Я предпочитаю иметь одну или две страницы кода в одном месте по когнитивным причинам. Я не верю в ультра тонко разделенный код.
То, что делают JVM или компиляторы, для меня менее актуально, если только нет горячей точки. Я не могу поверить, что есть какая-то фундаментальная причина для компиляторов не обнаруживать локально выбрасываемые и перехваченные исключения и просто обрабатывать их как очень эффективные goto
s на уровне машинного кода.
Что касается их использования в функциях управления потоком - i. е. для обычных случаев, а не для исключительных - я не могу понять, как они будут менее эффективны, чем множественные разрывы, тесты условий, возвраты, чтобы пройти через три кадра стека, в отличие от простого восстановления указателя стека.
Лично я не использую шаблон для стековых фреймов, и я вижу, как для этого потребуется изысканность дизайна, чтобы сделать это элегантно. Но используется экономно, все должно быть в порядке.
Наконец, что касается удивительных девственных программистов, это не является веской причиной. Если вы аккуратно познакомите их с практикой, они научатся ее любить. Я помню, что C ++ раньше удивлял и напугал программистов на Си.