Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 95
Текст из файла (страница 95)
1б.2. Цели и предположения При проектировании были сделаны предположения о том, что: а исключения используются преимушественно для обработки ошибок; о обработчики исключений встречаются реже, чем определения функций; о по сравнению с вызовами функций исключения возникают редко; о исключения — это понятие уровня языка, а не компилятора, и не стратегия обработки ошибок в узком смысле. Данные формулировки, равно как и изложенный ниже перечень желаемых возможностей, взяты со слайдов, на которых демонстрировалась эволюция проектирования с 1988 г. Обработка исключений: о задумывалась не как простая альтернатива механизму возврата — так предлагали некоторые, в особенности Дэвид Черитон (ПачЫ Спег1гоп), — а как специальный механизм для поддержки построения отказоустойчивых систем; о предназначается не для того, чтобы превратить каждую функцию в отказоустойчивую единицу.
Задумана как механизм, с помощью которого подсистема может выдерживать отказы даже тогда, когда составляющие ее функции написаны без соблюдения единой стратегии обработки ошибок; о не должна навязывать проектировщику одно единственное «правильное» представление о том, как следует обрабатывать ошибки. Цель обработки исключений — сделать язык более выразительным. На протяжении всей работы по проектированию возрастало влияние проектировщиков разнообразных систем и уменьшалось число предложений от сообщества пользователей языка.
За прошедшее с тех пор время наибольшее воздействие на проектирование обработки исключений в С++ оказала работа по отказоустойчивым системам, начатая в университете Ньюкасла в Англии Брайаном Рэпделлом и его коллегами и продолженная в других местах, В процессе проектирования обработки исключений были выработаны приведенные ниже критерии. 1.
Передача произвольного объема информации из точки, где возбуждено исключение, обработчику с сохранением данных о типе. 2. Отсутствие дополнительных издержек по скорости и по памяти в коде, не возбуждающем исключения. 3. Гарантии того, что любое возбужденное исключение будет перехвачено каким-то обработчиком.
ЛИИИИИИЕИ Цели и предположения 16.3. Синтаксис Как обычно, синтаксису было уделено достаточное внимание, и в результате я остановился на не слишком лаконичном варианте, в котором было три ключевых слова и обилие скобок: ьлс гн ( сту ( // начало лгу-блока теснгн д()) ) салол (ххзз) ( // начало обработчика исключения 4. Возможность группировать исключения так, чтобы можно было написать обработчик, перехватывающий не только одно, но и сразу несколько исключений.
5. Механизм, который по умолчанию будет правильно работать в многопоточной среде. 6. Механизм, допускающий взаимодействие с другими языками и особенно с С. 7. Простота использования. 8. Простота реализации. Большая часть этих критериев была воплощена в жизнь, но третий и восьмой принципы всегда оказывались либо требующими больших затрат, либо слишком ограничительными, поэтому в результате мы лишь приблизились к их реализации. Я считаю это неплохим результатом, принимая во внимание то, что обработка исключений — трудная задача, для решения которой программисту понадобится вся помощь, которую только можно получить.
Чрезмерно ревностный проектировщик языка мог бы включить средства или ограничения, которые лишь усложнили бы проектирование и реализапию отказоустойчивой системы. Думается, что отказоустойчивая система должна быть многоуровневой. Отдельная часть системы не может восстановиться после любого мыслимого сбоя и некорректных воздействий извне. Возможны ведь и крайние случаи: отказ питания или непроизвольное изменение содержимого ячейки памяти. В какой-то момент отдельный модуль системы может отказать, и тогда ситуация будет решаться на более высоком уровне этой системы. Например, вызванная функция может сообщить о катастрофической ошибке вызывающей; процесс может завершиться аномально и ч поручить ь другому процессу разобраться с последствиями; процессор может чобратиться за помощьюа к другому процессору. Компьютер может затребовать помощь у оператора-человека.
Подчеркнем в связи с этим: обработку ошибок следует проектировать так, чтобы у относительно простой программы, пользующейся сравнительно несложными средствами обработки исключений, был шанс выполнить свою задачу. Попытка дать такие средства, которые позволили бы одной монолитной программе восстановиться после любых ошибок, — неверный шаг, который ведет к стратегиям обработки настолько сложным, что они сами становятся источником ошибок. Обработка исключений ~ИИИИ // сюда попадаеи только тогда, когда случилось 'хх11' еггог(*ошибка в д(): хх11"); гесигп 22; 1пс д() ( // 1г (что то случилось! спгои хх11(); ) // возбудить исключение Ключевое слово г.гу, очевидно, избыточно, равно как и скобки ( !, если только сху-блок или обработчик ие состоят из нескольких предложений. Например, совсем несложно было бы разрешить такой синтаксис: 1пе Г(! гегпгп д() сакс)1 (хх11) ( // ие с++ еггог('ошибка в д(): хх11*) / гесвгп 22; )) ) Однако его так трудно объяснить, что я решил пойти на избыточность, дабы избавить персонал службы технической поддержки от вопросов запутавшихся пользователей.
Из-за традиционной нелюбви пользователей С к новым ключевым словам я всячески пытался уйти от этого, но все схемы, которые приходили на ум, оказывались слишком хитроумными или сбивающими с толку. Так, я пробовал использовать одно слово саг с)т и для возбуждения и для перехвата исключения.
Это можно было бы сделать логически непротиворечиво, но объяснить такую схему я бы не взялся. Слово с)тгом было выбрано отчасти из-за того, что более очевидные слова лайзе и в1дпа1 уже были заняты под функции из стандартной библиотеки С. 16.4. Группировка Поговорив с десятком пользователей десятка разных систем, поддерживающих в том или ином виде обработку исключений, я пришел к выводу, что необходимо уметь группировать исключения. Например, пользователь должен иметь возможносп перехватить любое исключение библиотеки ввода/вывода, даже ие зная точно, какими они бывают.
Есть, конечно, и обходные пути в ситуации, когда механизм группировки отсутствует. Например, тип исключения можно закодировать в виде лвнных, передаваемых вместе с единственным исключением. Допустимо просто перечислять исключения, логически входящие в одну группу, всякий раз, когда нужно обработать их все. Однако любая такая уловка расценивалась бы если и не всеми. то большинством людей как осложняющая сопровождения. Мы с Эндрю Кенигом попробовали было схему, основанную на том, что группы создавались динамически с помощью конструкторов для объектов исключений.
° ИИИИИ~ Группировка с1авв Магпегг ( ); с1авв Очегг1ои: риЫфс Магпегг ( ); с1авв ппбегг1ои : рпЫ1с магпегг ( ); с1авв зегобгчгде: рагс Магпегг ( ); // 1б с() ( сгу [ г(); ) саксо (Онегб1ои) ( // обработать исключение типа Очегг1ои или любого производного // от него ) саксо (Маспегг) ( // обработать любое исключение типа Маспегг, кроме Очегб1ом ) ) Позже обнаружилось, что множественное наследование (см. главу 12) дает очень красивое решение трудных задач классификации.
Например, можно было объявить ошибку при работе с файлом на сетевом диске так: с1авв пегиогк 111е егг : риЫ)с пегмогк егг, риЫ1с Г11е вувсею. егг ( ); Исключение типа песмог)с Ше егг может обработать как обработчик сетевых ошибок, так и ошибок в файловой системе. Первым на это мне указал Даниэль Уэйнреб (Паше) Же)пгеЪ). 16.5. Управление ресурсами Главным в проекте обработки исключений был вопрос об управлении ресурсами. Скажем, если функция захватывает некоторый ресурс, может ли язык выполнить часть работы по освобождению ресурса при выходе, даже если произошло исключение? Рассмотрим пример, взятый из 12пд1: но1б иве г"11е(сопев спаг* Гп) Г1ЬЕ* г" = горел(гп,"и")/ // открыть файл с именем гп Однако это выпадало из стиля, принятого в других частях С++, и многие, в том числе Тед Голдстейн и Питер Дойч (Регег Г)енгвсЬ), отмечали, что такие группы по сути эквивалентны иерархии классов.
Поэтому мы остановились на схеме, навеянной языком МЕ: возбужденное исключение-объект перехватывается обработчиком, в объявлении которого говорится, что он может принимать объекты такого типа. При этом обычные правила инициализации С++ позволяют обработчику объектов типа в перехватывать объекты любого класса г), производного от в. Например: Обработка исключений БИИИИВИМ // использовать ( бс1ове(2); // закрыть файл Гп ) Выглядит разумно. Но если между вызовами бореп ( ) и бс1озе ( ) произойдет ошибка, из-за возникшего исключения выход из функции пзе б11е может быть выполнен без вызова бс 1о не ( ) . То же самое может случиться и в языках, не поддерживающих исключения.