Некоторые языки (такие как C ++ и ранние версии PHP) не поддерживают finally
часть try ... catch ... finally
конструкции. Когда- finally
либо необходимо? Поскольку код в нем всегда выполняется, почему бы не разместить этот код после try ... catch
блока без finally
предложения? Зачем использовать один? (Я ищу причину / мотивацию для использования / неиспользования finally
, а не причину покончить с «уловом» или почему это допустимо.)
exception-handling
Agi Hammerthief
источник
источник
Ответы:
В дополнение к тому, что сказали другие, также возможно исключение, которое будет добавлено в условие catch. Учти это:
В этом примере
Cleanup()
функция никогда не запускается, потому что в предложении catch генерируется исключение, и следующий перехват в стеке вызовов перехватывает это. Использование блока finally устраняет этот риск и делает код более чистым для загрузки.источник
Как уже упоминали другие, нет гарантии, что код после
try
оператора будет выполняться, если вы не поймаете все возможные исключения. Тем не менее, это:можно переписать 1 как:
Но последнее требует, чтобы вы перехватили все необработанные исключения, продублировали код очистки и не забыли перезапустить. Так
finally
что не обязательно , но это полезно .C ++ не имеет,
finally
потому что Бьярн Страуструп считает, что RAII лучше или, по крайней мере, достаточно для большинства случаев:1 Конкретный код для перехвата всех исключений и переброса без потери информации трассировки стека зависит от языка. Я использовал Java, где трассировка стека фиксируется при создании исключения. В C # вы бы просто использовали
throw;
.источник
handleError()
втором случае, нет?catch (Throwable t) {}
с помощью блока try .. catch вокруг всего начального блока (также для отловаhandleError
handleErro();
что сделает его еще лучшим аргументом относительно того, почему наконец полезны блоки (даже если это не был первоначальный вопрос).finally
, что намного более нюансировано.try
находится внутриcatch
для конкретного исключения. Во-вторых, возможно, вы не знаете, сможете ли вы успешно обработать ошибку, пока не изучите исключение, или что причина исключения также не позволяет вам обработать ошибку (по крайней мере, на этом уровне). Это довольно часто при вводе / выводе. Перебрасывание происходит потому, что единственный способ гарантировать выполнениеcleanUp
- перехватывать все , но исходный код позволит исключениям, происходящим вcatch (SpecificException e)
блоке, распространяться вверх.finally
блоки обычно используются для очистки ресурсов, которые могут помочь с удобочитаемостью при использовании нескольких операторов возврата:против
источник
finally
. (Я бы использовал код, как во втором блоке, так как несколько операторов возврата не приветствуются там, где я работаю.)Как вы, очевидно, уже догадались, да, C ++ предоставляет те же возможности без этого механизма. Как таковой, строго говоря, механизм
try
/finally
не является действительно необходимым.Тем не менее, обход без него накладывает некоторые требования на то, как устроен остальной язык. В C ++ тот же набор действий воплощен в деструкторе класса. Это работает в основном (исключительно?), Потому что вызов деструктора в C ++ является детерминированным. Это, в свою очередь, приводит к некоторым довольно сложным правилам жизни объектов, некоторые из которых явно не интуитивны.
Большинство других языков предоставляют некоторую форму сборки мусора. Хотя в сборке мусора есть противоречивые вещи (например, его эффективность по сравнению с другими методами управления памятью), в общем-то, это не так: точное время, когда объект будет «очищен» сборщиком мусора, не связано напрямую к объему объекта. Это предотвращает его использование, когда очистка должна быть детерминированной, или когда она просто необходима для правильной работы, или когда имеешь дело с ресурсами, настолько ценными, что их очистка не будет задерживаться произвольно.
try
/finally
позволяет таким языкам справляться с ситуациями, требующими детерминированной очистки.Я думаю, что те, кто заявляет, что синтаксис C ++ для этой возможности «менее дружественен», чем Java, скорее упускают суть. Хуже того, они упускают гораздо более важный момент в разделении ответственности, который выходит далеко за рамки синтаксиса и имеет гораздо больше общего с тем, как разрабатывается код.
В C ++ эта детерминированная очистка происходит в деструкторе объекта. Это означает, что объект может быть (и обычно должен быть) предназначен для очистки после себя. Это относится к сути объектно-ориентированного проектирования - класс должен быть спроектирован так, чтобы обеспечивать абстракцию и реализовывать свои собственные инварианты. В C ++ это делается именно так - и один из инвариантов, для которых он предусмотрен, заключается в том, что при уничтожении объекта ресурсы, контролируемые этим объектом (все они, а не только память), будут уничтожены правильно.
Java (и аналогичные) несколько отличаются. Хотя они поддерживают (своего рода) функцию,
finalize
которая теоретически может обеспечить аналогичные возможности, поддержка настолько слаба, что в принципе ее невозможно использовать (и фактически она практически никогда не используется).В результате, а не класс сам в состоянии сделать необходимую очистку, то клиент класса должен предпринять шаги , чтобы сделать это. Если мы проведем достаточно близорукое сравнение, на первый взгляд может показаться, что это различие довольно незначительное, и в этом отношении Java вполне конкурентоспособна с C ++. В итоге получается что-то подобное. В C ++ класс выглядит примерно так:
... и код клиента выглядит примерно так:
В Java мы обмениваемся немного больше кода, где объект используется для немного меньше в классе. Это изначально выглядит довольно даже компромисс. На самом деле это далеко не так, потому что в большинстве типичных программ мы определяем класс только в одном месте, но используем его во многих местах. Подход C ++ означает, что мы пишем этот код только для обработки очистки в одном месте. Подход Java означает, что мы должны написать этот код для многократной очистки, во многих местах - в каждом месте, где мы используем объект этого класса.
Короче говоря, подход Java в основном гарантирует, что многие абстракции, которые мы пытаемся предоставить, являются «утечками» - любой и каждый класс, который требует детерминированной очистки, обязывает клиента класса знать о деталях того, что нужно очистить и как выполнить очистку , а не те детали, которые скрыты в самом классе.
Хотя я назвал это «подходом к Java» выше,
try
/finally
и подобные механизмы под другими именами не полностью ограничены Java. Для одного яркого примера большинство (все?) Языков .NET (например, C #) предоставляют то же самое.Недавние итерации как Java, так и C # также обеспечивают нечто среднее между «классической» Java и C ++ в этом отношении. В C # объект, который хочет автоматизировать свою очистку, может реализовать
IDisposable
интерфейс, который предоставляетDispose
метод, который (по крайней мере, неопределенно) похож на деструктор C ++. Хотя это может быть использовано черезtry
/finally
как в Java, C # немного автоматизирует задачу с помощьюusing
оператора, который позволяет вам определять ресурсы, которые будут создаваться при входе в область и уничтожаться при выходе из области. Хотя уровень автоматизации и определенности, предоставляемый C ++, все еще значительно ниже, это все же является существенным улучшением по сравнению с Java. В частности, дизайнер класса может централизовать детали того, какраспоряжаться классом при его реализацииIDisposable
. Все, что остается клиентскому программисту, - это меньшее бремя написанияusing
оператора, чтобы гарантировать, чтоIDisposable
интерфейс будет использоваться тогда, когда он должен быть. В Java 7 и новее имена были изменены, чтобы защитить виновных, но основная идея в основном идентична.источник
Не могу поверить , что никто не поднял это (не каламбур) - вам не нужно в поймать положение!
Это совершенно разумно:
Никакое предложение catch нигде не является зрением, потому что этот метод не может сделать ничего полезного с этими исключениями; их оставляют для передачи обратно вверх по стеку вызовов в обработчик, который может . Поймать и перебросить исключения в каждом методе - плохая идея, особенно если вы просто перебрасываете одно и то же исключение. Это полностью идет вразрез с тем, как должна работать структурированная обработка исключений (и довольно близка к возвращению «кода ошибки» из каждого метода, просто в «форме» исключения).
Что этот метод действительно нужно сделать, хотя, убирать за собой, так что «внешний мир» никогда не должен ничего знать о беспорядке , что он попал в себя. Предложение finally делает именно это - независимо от того, как ведут себя вызываемые методы, предложение finally будет выполнено «по пути» метода (и то же самое верно для каждого предложения finally между точкой, в которой выбрасывается исключение, и возможное предложение catch, которое его обрабатывает); каждый запускается как стек вызовов "раскручивается".
источник
Что произойдет, если и будет выброшено исключение, которое вы не ожидали. Попытка завершится в середине, и предложение catch не будет выполнено.
Последний блок должен помочь с этим и гарантировать, что независимо от исключения произойдет очистка.
источник
finally
, так как вы можете предотвратить "неожиданные" исключенияcatch(Object)
илиcatch(...)
перехватить все.Некоторые языки предлагают как конструкторы, так и деструкторы для своих объектов (например, C ++, я считаю). С этими языками вы можете делать большинство (возможно, все) того, что обычно делается в
finally
деструкторе. Таким образом, в этих языкахfinally
пункт может быть излишним.В языке без деструкторов (например, Java) трудно (возможно, даже невозможно) добиться правильной очистки без
finally
предложения. NB. В Java естьfinalise
метод, но нет гарантии, что он когда-либо будет вызван.источник
finalise
но я бы предпочел не вдаваться в политические аргументы вокруг деструкторов / финалов в настоящее время.finalise
но с расширяемым вкусом и механизмом, похожим на упс - очень выразительный и сравнимый сfinalise
механизмом других языков.Попробуй наконец и попробуй catch - это две разные вещи, которые имеют только ключевое слово: "try". Лично я хотел бы видеть это по-другому. Причина, по которой вы видите их вместе, в том, что исключения создают «скачок».
И попытка наконец-то предназначена для запуска кода, даже если поток программирования выскакивает. Будь то из-за исключения или по любой другой причине. Это отличный способ приобрести ресурс и убедиться, что он очищен после того, как вам не нужно беспокоиться о скачках.
источник
try catch
но не поддерживаетtry finally
; код, использующий последнее, преобразуется в код, использующий только первое, путем копирования содержимогоfinally
блока во всех местах кода, где его может потребоваться выполнить.Поскольку этот вопрос не определяет язык C ++ как язык, я рассмотрю сочетание C ++ и Java, поскольку они используют другой подход к уничтожению объектов, который предлагается в качестве одной из альтернатив.
Причины, по которым вы можете использовать блок finally, а не код после блока try-catch
вы рано возвращаетесь из блока try: учтите это
по сравнению с:
Вы возвращаетесь рано из блока (ов) вылова: Сравнить
против:
Вы отбрасываете исключения. Для сравнения:
против:
Эти примеры не делают это слишком плохим, но часто у вас есть несколько взаимодействующих случаев и более одного типа исключений / ресурсов в игре.
finally
может помочь предотвратить превращение вашего кода в сложный кошмар обслуживания.Теперь в C ++ они могут быть обработаны объектами, основанными на области видимости. Но у ИМО есть два недостатка в этом подходе: 1. синтаксис менее дружественный. 2. Порядок строительства, противоположный порядку разрушения, может сделать вещи менее ясными.
В Java вы не можете подключить метод finalize для выполнения очистки, поскольку вы не знаете, когда это произойдет - (ну, вы можете, но это путь, заполненный забавными условиями гонки - JVM имеет много возможностей для принятия решения, когда он разрушает вещи - часто это не то, когда вы ожидаете - или раньше или позже, чем вы могли бы ожидать - и это может измениться, когда компилятор горячей точки начинает ... вздох ...)
источник
Все, что логично «необходимо» в языке программирования, это инструкции:
Любой алгоритм может быть реализован с использованием только приведенных выше инструкций, все другие языковые конструкции предназначены для того, чтобы программы могли легче писать и быть более понятными для других программистов.
Смотрите Oldie Worldy Computer для реального оборудования, используя такой минимальный набор инструкций.
источник
На самом деле больший пробел для меня обычно в языках, которые поддерживают,
finally
но не имеют деструкторов, потому что вы можете смоделировать всю логику, связанную с «очисткой» (которую я разделю на две категории), с помощью деструкторов на центральном уровне, не занимаясь очисткой вручную. логика в каждой соответствующей функции. Когда я вижу, что код на C # или Java выполняет такие вещи, как ручное разблокирование мьютексов и закрытие файлов вfinally
блоках, это выглядит как устаревший код, похожий на код C, когда все это автоматизировано в C ++ с помощью деструкторов, что освобождает людей от этой ответственности.Тем не менее, я все равно нашел бы небольшое удобство, если бы C ++ был включен,
finally
и это потому, что есть два типа очистки:Второе, по крайней мере, не так интуитивно отображается в идее уничтожения ресурса, хотя вы можете сделать это просто отлично с защитой области видимости, которая автоматически откатывает изменения, когда они уничтожаются до того, как будут зафиксированы. Там ,
finally
возможно , обеспечивает по крайней мере немного (только на подростка бит) более простой механизм для работы , чем области видимости охраны.Тем не менее, еще более простой механизм будет
rollback
блоком, который я никогда не видел ни на одном языке раньше. Это своего рода несбыточная мечта, если я когда-нибудь разработал язык, который включал бы обработку исключений. Это будет похоже на это:Это был бы самый простой способ моделирования откатов побочных эффектов, в то время как деструкторы в значительной степени являются идеальным механизмом для очистки локальных ресурсов. Теперь он сохраняет только пару дополнительных строк кода из решения охраны области, но причина, по которой я так хочу увидеть язык с этим, заключается в том, что откат побочных эффектов, как правило, является наиболее игнорируемым (но самым хитрым) аспектом обработки исключений. в языках, которые вращаются вокруг изменчивости. Я думаю, что эта функция побудит разработчиков задуматься о правильной обработке исключений с точки зрения отката транзакций всякий раз, когда функции вызывают побочные эффекты и не завершаются, и в качестве дополнительного бонуса, когда люди видят, как сложно выполнить откат должным образом, они могут предпочесть написание большего количества функций без побочных эффектов.
Есть также некоторые неясные случаи, когда вы просто хотите делать разные вещи, независимо от того, что происходит при выходе из функции, независимо от того, как она вышла, например, при записи в журнал отметки времени. Там
finally
является , возможно, самым простым и идеальным решением для работы, так как пытается создать экземпляр объекта использовать только его деструктор с единственной целью регистрации метки времени просто чувствует себя действительно странно (хотя вы можете сделать это просто отлично и очень удобно с лямбдой ).источник
Как и многие другие необычные вещи в языке C ++, отсутствие
try/finally
конструкции является недостатком дизайна, если вы даже можете назвать его так, что в языке, который, как часто кажется, вообще не было выполнено никакой реальной работы по дизайну .RAII (использование детерминистского вызова деструктора на основе области действия для объектов на основе стека для очистки) имеет два серьезных недостатка. Во-первых, это требует использования стековых объектов , которые являются мерзостью, нарушающей принцип подстановки Лискова. Есть много веских причин, почему ни один другой язык ОО до или после C ++ не использовал их - в рамках epsilon; D не считается, поскольку он в значительной степени основан на C ++ и в любом случае не имеет доли на рынке - и объяснение проблем, которые они вызывают, выходит за рамки этого ответа.
Во-вторых, что
finally
может сделать, так это надстройка уничтожения объектов. Многое из того, что делается с RAII в C ++, будет описано на языке Delphi, который не имеет сборки мусора, по следующей схеме:Это шаблон RAII, сделанный явным; если бы вы создали подпрограмму C ++, которая содержала бы только эквивалент первой и третьей строк выше, то, что сгенерировал бы компилятор, в конечном итоге выглядело бы так, как я написал в его базовой структуре. И поскольку это единственный доступ к
try/finally
конструкции, предоставляемой C ++, разработчики C ++ в конечном итоге получают довольно близорукое представлениеtry/finally
: когда все, что у вас есть, это молоток, все начинает выглядеть, так сказать, деструктором.Но есть и другие вещи, которые опытный разработчик может сделать с
finally
конструкцией. Речь идет не о детерминированном разрушении, даже перед лицом возникающего исключения; речь идет о детерминированном выполнении кода , даже при возникновении исключения.Вот еще одна вещь, которую вы обычно можете увидеть в коде Delphi: объект набора данных с привязанными к нему пользовательскими элементами управления. Набор данных содержит данные из внешнего источника, а элементы управления отражают состояние данных. Если вы собираетесь загрузить кучу данных в свой набор данных, вам нужно временно отключить привязку данных, чтобы она не вызывала странных вещей в вашем пользовательском интерфейсе, пытаясь обновлять ее снова и снова с каждой новой записанной записью. , так что вы бы закодировали это так:
Понятно, что здесь нет разрушаемого объекта, и он не нужен. Код прост, сжат, явен и эффективен.
Как это будет сделано в C ++? Ну, во-первых, вы должны написать весь класс . Наверное, это называется
DatasetEnabler
или что-то в этом роде. Все его существование будет в качестве помощника RAII. Тогда вам нужно будет сделать что-то вроде этого:Да, эти явно лишние фигурные скобки необходимы для управления надлежащей областью видимости и обеспечения немедленного включения набора данных, а не в конце метода. То, что вы в итоге получите, не займет меньше строк кода (если вы не используете египетские скобки). Это требует создания лишнего объекта, который имеет накладные расходы. (Разве код C ++ не должен быть быстрым?) Он не является явным, а использует магию компилятора. Выполненный код нигде не описывается в этом методе, но вместо этого находится в совершенно другом классе, возможно, в совершенно другом файле . Короче говоря, это ни в коем случае не лучшее решение, чем возможность написать
try/finally
блок самостоятельно.Такая проблема достаточно распространена в языковом дизайне, и для нее есть название: инверсия абстракции. Это происходит, когда высокоуровневая конструкция строится поверх низкоуровневой конструкции, и тогда низкоуровневая конструкция напрямую не поддерживается в языке, что требует от тех, кто хочет использовать ее, для ее повторной реализации с точки зрения высокоуровневая конструкция, часто с большими затратами как на читаемость кода, так и на эффективность.
источник