И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 29
Текст из файла (страница 29)
Информацияо произошедшей «аварии» записывается в экземпляр типа данных.Простейший случай — целочисленный тип (ошибке сопоставляетсяцелое значение — код ошибки) или строка (const char* — ошибкесопоставляется текст сообщения).В реальных программах информации, содержащейся в значенияхбазисных типов, недостаточно для адекватной реакции на ошибку.В современных программах рекомендуется использовать для определения ИС специальный класс exception из стандартной библиотеки C++. Отметим, что современный компилятор вставляет код погенерации исключений этого класса, так что полезно «собственные»ИС делать производными от exception классами, что позволяетединообразно обрабатывать «собственные» и стандартные ИС.Языки Java и C# используют для определения ИС специальныеклассы.
В Java тип любой ИС должен быть производным от классаThrowable, а в C# — от Exception.Все классы ИС делятся на два вида: пользовательские и системные.Возбуждение пользовательских ИС происходит в коде пользователя,а системных ИС — в коде, генерируемом компилятором. Пользовательские ИС рекомендуется делать производными от специальныхклассов: в C++ —от exception, в C# — от ApplicationException,в Java — от Exception (подробнее см. подразд. 9.3).Возбуждение ИС происходит с помощью специального операторавозбуждения исключения throw:C++ : throw выражение;С#, Java : throw new Х();Здесь выражение — произвольное выражение C++, а X — допустимый класс ИС. Значение, которое вырабатывается операторомвозбуждения ИС, рассматривается как объект, называемый текущимисключением.Система времени выполнения содержит специальную область памяти для хранения текущего исключения.
В C++ текущее исключениекопируется в выделенную область памяти (при копировании объектакласса будет работать конструктор копирования). В других языкахоперация new сразу размещает объект в выделенной памяти.147Как только возникает ИС, начинается процесс ее распространения.9.3. Распространение и обработкаисключительных ситуацийИсключения распространяются по принципу динамической ло вушки. Этот принцип заключается в том, что выбирается реакцияна ИС (ловушка), ближайшая по динамической цепочке вызовов.Вспомним, что единицей области действия переменных являетсяблок.
В соответствии с принципом динамической ловушки блок,в котором возникло исключение, является аварийным и завершается(аварийно). Все локальные переменные, объявленные в аварийномблоке, уничтожаются. Процесс уничтожения переменных в аварийном блоке называется сверткой стека.
В C++ во время свертки стекадля локальных переменных—объектов работают деструкторы. Еслив этот момент в деструкторе возникнет новое исключение, то местадля него уже нет (оно занято текущим исключением). Программав этом случае завершается аварийно.Если аварийный блок содержит ловушки (обработчики), то происходит поиск подходящей ловушки. Если ловушка не найдена (илиловушек вообще не было), то блок, из которого вызван аварийныйблок, становится в свою очередь аварийным (получается, что вызовэтого блока стал эквивалентен выполнению оператора возбужденияИС) и процесс распространения повторяется уже для этого блока. Такпроисходит до тех пор, пока либо найдется ловушка, либо произойдетвыход из последнего блока (в main).
В последнем случае программазавершается аварийно с выдачей соответствующей диагностики.Рассмотрим объявление ловушек и процесс поиска ловушек.В некоторых языках ловушки можно размещать в любых блоках,но в рассматриваемых языках для размещения ловушек служит специальный блок, называемый блоком с ловушками, или try-блоком.Вид блока с ловушками следующий:tryблоксписок-ловушекСписок ловушек упорядочен. Каждая ловушка в списке имеетвидcatch (тип имя) блокилиcatch(тип) блокилифинальная-ловушка148Поиск ловушек при выходе из аварийного блока происходит следующим образом. Пусть тип текущей ИС — т.
Тогда ловушки перебираются по порядку и тип из ловушки сопоставляется с типом т.Единственное преобразование, которое можно применять, — преобразование из производного класса в базовый. Таким образом, ловушка базового класса подходит исключению любого производногокласса.
Ловушка класса Throwable в Java или Exception в C#гарантированно «ловит» любую ИС.В Java и C# правила отождествления крайне просты (посколькуречь идет о сопоставлении ссылок на классы из одной иерархии),в C++ эти правила посложнее (учитывая, что тип ИС из ловушкиможет быть ссылочным, указательным, значением и т. п.), но базовыйпринцип поиска прост — отыскиваются типы, которым принадлежиттип текущего исключения (вспомним, что все объекты производноготипа одновременно принадлежат базовому).Последней может быть финальная ловушка. Она подходит длялюбого типа.Финальная ловушка в C++ имеет видcatch (...) блокФинальная ловушка есть и в C# (хотя без нее можно было обойтись):catch блокОбработка исключенияКак только ловушка найдена, начинается обработка ИС, т.е.
выполнение блока ловушки. Если у параметра ловушки есть имя, тотекущее исключение ассоциируется с параметром ловушки (по правилам передачи параметров подпрограмм). Если имя опущено, то этоозначает, что информация об ИС игнорируется в блоке ловушки.Если блок ловушки завершился нормально, то ИС считается обработанной (т.е. исправленной), аварийное состояние завершаетсяи объект текущего исключения уничтожается (в C++ сработает деструктор).Вопросы, как именно следует располагать ловушки и какой кодони должны содержать, весьма и весьма сложные и выходят за рамкиданного курса [17, 23].Ловушки могут нормально завершаться как через обычный выходиз блока ловушки, так и оператором возврата. При обычном выходеиз блока ловушки выполнение продолжается с первого оператора,следующего за t r y -блоком с ловушками.
Оператор возврата в блокеловушки рассматривается как возврат из функции, внутри которойнаходится t r y -блок с ловушками.149Возможна, однако, ситуация, когда и блок ловушки завершаетсяненормально, и процесс распространения исключения продолжается.Это может быть в двух следующих случаях:• возбуждение новой ИС (переупаковка);• частичная обработка.При возбуждении новой ИС внутри блока ловушки располагаетсяоператор возбуждения ИС с новым объектом — текущим исключением. Старый объект уничтожается и замещается новым объектом.Распространение ИС продолжается «вверх» по цепочке вызововс новым текущим исключением. Такая ситуация возникает довольно часто, когда «авария» не исправляется, но необходимо изменитьинформацию об исключении, например перевести на другой язык,изложить сообщение об ошибке в других терминах. Часто необходимо «переупаковать» ИС из «чужого» класса в класс ИС своегоприложения и т.
д.При частичной обработке исключение также считается необработанным, но текущий объект остается прежним. Частичная обработка завершается выполнением укороченной формы операторавозбуждения ИС:throw;После выполнения этого оператора распространение ИС продолжается «вверх» по цепочке вызовов со старым текущим исключением.9.4. Особенности реализации исключенийв языках C++, C# и JavaЯзык C++ не требует обязательной обработки ИС. Так, некоторыепрограммы или хотя бы некоторые функции не требуют защиты.Особенно это касается научно-исследовательских программ, для которых эффективность важнее надежности. Не использовать механизмобработки ИС легко — надо просто не пользоваться t r y -блоками.В этом случае возникновение ИС (например, стандартной) немедленно завершает программу аварийным образом.В C# и Java любая программа поддерживает обработку ИС.
В случае возбуждения необработанной ИС программа также завершаетсяаварийно, но печатается трассировочная информация по всей динамической цепочке вызовов.Спецификация ИСВ C++ и Java существует концепция спецификации ИС.150Спецификация ИС обусловлена очевидным фактом: для тогочтобы адекватно реагировать на ошибку, следует понимать, какиеошибки могут возникать при вызове какой-либо функции.
Как ужеотмечалось, вызов функции (т.е. выполнение ее тела-блока) можетпривести к возникновению (точнее, распространению) необработанной ИС. С точки зрения вызывающего блока совершенно всеравно, то ли ИС класса X возникла внутри вызова функции f ()и распространилась за пределы ее тела, то ли вместо вызова f () стоитоператор throw X () ;. Эффект в обоих случаях одинаковый.Язык C++ позволяет описать, какие именно ИС могут возникатьпри вызове функции. Это делается с помощью спецификации ИС:прототип-функции спецификация-ИС тело-функцииСпецификация ИС имеет видt h r o w (список-имен-типов-ИС)Спецификация ИС объявляет, что при вызове функции могутвозникнуть только ИС из списка.
Это не значит, что внутри функции не могут возникать другие ИС (это возможно, просто функцияобъявляет, что все ИС не из списка либо не возникнут внутри нееникогда, либо будут в ней корректно обработаны). А вот исключенияиз списка не только могут возникать, но и не будут обрабатыватьсяэтой функцией.Пустой список ИС говорит о том, что функция декларирует, чтоона не пропустит ни одной ИС. Вызов такой функции максимальнобезопасен.Например:void f() throw (X) { ... }int M yC lass::getLength () throw{ ...(MemoryErr,FileErr)}void g(int k) t h r o w () {}Если программист вызывает функцию со списком исключений,то у него возникают две альтернативы по каждой ИС из списка.
Вопервых, он может обработать эту ИС (как говорят, подавить ИС),во-вторых, он может добавить неподавленную ИС в свой список ИС(например, используя функцию f из предыдущего примера):void suppress () throw (){try{f(); // здесь может возникнуть ИС типа X}catch(X ex){// успешная обработка ИС типа X151}}void pass () throw (X){f(); // здесь может возникнуть ИС типа X}Правда, возможен еще один (недостойный) вариант — проигнорировать ситуацию:void ignore(){f(); // здесь может возникнуть ИС типа X}Компилятор C++ не считает это ошибкой, поскольку спецификация ИС необязательна.Зачем специфицировать ИС, если делать это необязательно? Можно задокументировать этот факт без всяких спецификаций.Дело в том, что если при вызове функции со списком ИС все-такипроизойдет неспецифицированное исключение, то программа будетнемедленно аварийно завершена с выдачей адекватной диагностики.