Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 96
Текст из файла (страница 96)
Например, к таким же печальным последствиям приведет вызов функции 1опдзар ( ) из стандартной библиотеки С. Если мы хотим разрабатывать отказоустойчивые системы, эту проблему придется снимать. Примитивное решение выглядит так: уо1б пве 111е(сопел сваг* бп) ( Г1пд* б = горел(бп,"г"); лгу ( // использовать 1 // открыть файл с именем бп ) сакс)з (...) ( // перехватить все исключения бс1озе(2); // закрыть файл бп с)эгон; // возбудить повторно ) бс1ове(Г); // закрыть файл 2п ) чогб пзе() ( // захватить ресурс 1 // // захватить ресурс и // испольэовать ресурсы // освободить ресурс и // О освободить ресурс 1 Весь код для работы с файлом заключен в блок сгу, который перехватывает любое исключение, закрывает файл и возбуждает то же исключение повторно.
К сожалению, данное решение многословно и потенциально имеет большие затраты. К тому же необходимость писать длинные повторяющиеся фрагменты утомляет программиста и он может наделать ошибок. Можно было слегка сократить код, предоставив в распоряжение программиста некоторый механизм финальной обработки. Это позволило бы уйти от дублирования кода освобождения ресурса (в данном случае бс1озе ( б) ), но никак не решило бы принципиальную проблему: для написания отказоустойчивого кода требуется специальная и более сложная, чем обычно, техника. Впрочем, есть и более изящное решение.
В общем виде задача выглядит так: ИИИИИИИЙ Управление ресурсами с1аьв Г11евгг Г1ЬЕ* р; рп)з11с: Г11еРГг(сопят с)заг* и, сопвс сваг* а) ( р = 2ореп(п,а); ) Гвьеркг(Г1ЬЕ* рр) ( р = рр: -Ггаерсг() ( Ес1ове(р); орегасог Г1ЬЕ*() ( гекогп р; ); Мы можем сконструировать Г11сргг, зная либо Г1ЬЕ*, либо аргументы, которые обычно передаются борен ( ) . В любом случае объект Г11ерс г будет разрушен при выходе из области действия, а его деструктор закроет файл. Теперь наша программа сокрашается до минимального размера: чо1с пве г11е(сопзс сваг* гп) ( Г11ерсг Е(Гп,"г"); // открыть файл с именем Еп // использовать Е // Файл Еп закрывается неявно Деструктор вызывается независимо от того, как произошел выход из функции: обычно или в результате исключения.
Данный прием я называю «захват ресурса — это инициализация». Он может работать с частично сконструированными объектами и таким образом решает довольно трудную проблему: что делать, если ошибка произошла в конструкторе [см. [Коеп)я, 1990] или [2п(Ц). 16.5.1. Ошибки в конструкторах Для некоторых пользователей важнейшим аспектом исключений является то, что они дают обший механизм извещения об ошибках, произошедших в конструкторе.
рассмотрим конструктор класса Г11ерсг: в нем не проверяется, открыт ли файл корректно или нет. Вот наиболее аккуратная запись: Г11ерсг::Г11ерсг(сопвс сваг* и, сопзс сваг* а) ( Ьг ((р = горев(п,а) ) == 0) ( // файл не открылся - что делать? Без исключений нет простого способа сообщить об ошибке: конструктор не возвращает значения. Поэтому приходилось изобретать обходные пути, например переводить янаполовину сконструированные» объекты в ошибочное состояние, Как правило, важно, чтобы ресурсы освобождались в порядке, обратном их захвату.
Это весьма напоминает поведение локальных объектов, которые создаются конструкторами и уничтожаются деструкторами. Стало быть, мы можем решить проблему захвата и освобождения ресурсов путем разумного использования объектов классов, имею)цих конструкторы и деструкторы. Например, удалось бы определить класс Г11ерсг, ведуший себя как Г1ье*: ЙИИИИ11 Обработка исключений оставлять флаги в глобальных переменных и т.д. Как ни странно, на практике зто редко становилось проблемой.
Однако исключения дают общее решение: г11ерсг: гв11евгг(сспвг сваг* и, ссавг сваг* а) ( гг ((р = горев(п,а) ) == 0) // ой) файл не открылся гЬгоы Ореп ГагГес)(а,а); ) Механизм же обработки исключений в С++ гарантирует, что частично сконструированные объекты корректно уничтожаются, то есть полностью сконструированные подобъекты разрушаются, а те подобъекты, которые остались несконструированными, — нет. Это позволяет автору конструктора сосредоточиться на обработке ошибки в том объекте, в котором она обнаружена.
Подробнее см. 12п(1, 49.4.1!. 16.6. Возобновление или завершение? При проектировании механизма обработки исключений наиболее спорным оказался вопрос, следует ли поддержать семантику возобновления или прекращения исполнения. Иными словами, может ли обработчик исключения потребовать возобновления исполнения с того момента, где было возбуждено исключение? Например, было бы хорошо, если бы подпрограмма, вызванная для обработки ошибки нехватки памяти, могла найти дополнительную память и вернуть управление? Или если бы подпрограмма, вызванная для обработки деления на нуль, вернула некоторое определенное пользователем значение? А если бы подпрограмма, вызванная потому, что в дисководе нет гибкого диска, могла попросить пользователя вставить диск, а затем продолжила выполнение? Поначалу я думал так: вПочему бы и нет? Ведь сразу видно несколько ситуаций, в которых я мог бы воспользоваться возобновлением».
Но в последующие четыре года моя точка зрения некоторым образом изменилась, н потому механизм обработки исключений в языке С++ поддерживает так называемую модель с завершением. Главные споры по поводу возобновления н завершения разгорелись в комитете АХВ! С++. Вопрос обсуждался и на заседаниях всего комитета, и в рабочей группе по расширениям, и на вечерних технических обсуждениях, и в списках для рассылки по электронной почте. Споры продолжались с декабря 1989 г., когда был сформирован комитет А1ч81 С++, до ноября 1990 г, Тема вызвала большой интерес и у всего сообщества пользователей С++. В комитете семантику возобновления отстаивали преимущественно Мартин О'Риордан и Майк Миллер. Эндрю Кениг, Майк Вило, Тед Голдстейн, Дзг Брюк, Дмитрий Ленков и я высказывались за семантику завершения. Являясь председателем рабочей группы по расширениям, я направлял дискуссию.
Результат был таков: 22 человека против одного за семантику завершения. Достичь результата удалось после долгого заседания, на котором представители компаний Г)ЕС, Бпп, Техав 1пзггпшепгв и 1ВМ демонстрировали Возобновление или завершение? 11виииввю экспериментальные данные. Затем последовало одобрение этого предложения на заседании всего комитета (4 человека из 30 против, остальные — за), в таком виде, то есть с семантикой завершения, оно и опубликовано в АКМ.
После длительных дебатов на встрече в Сиэтле, состоявшейся в июле 1990 г., я свел воедино все доводы в пользу семантики возобновления: о более общий механизм (более мощный, включает и семантику завершения); о унифицирует сходные концепции и реализации; о необходим для очень сложных динамичных систем (например, для 0$/2); о ненамного более сложен и расточителен при реализации; о если механизма, включающего семантику возобновления нет, его приходится имитировать; о дает простое решение проблемы истощения ресурсов. Аналогично я обобщил доводы, высказанные в пользу семантики завершения: о более простой, концептуально чистый и дешевый способ; о позволяет строить системы, более легкие в управлении; о обладает достаточной мощностью для решения любых задач; о позволяет избежать неудачных или не очень законных программных приемов; о противостоит негативному опыту, накопленному при работе с семантикой возобновления.
В обоих перечнях намного упрощена суть споров, основательных и изобилующих техническими деталями. Иногда полемика носила весьма эмоциональный характер. Менее сдержанные участники кричали, что сторонники семантики завершения хотят навязать произвольные ограничения на приемы программирования. Очевидно, что вопрос о возобновлении и завершении затрагивает глубинные принципы проектирования программного обеспечения.
Число сторонников той и другой точек зрения никогда не было одинаковым, однако на любой встрече отстаивающих семантику завершения было примерно в четыре раза больше. Среди аргументов в пользу возобновления чаще других повторялись такие: о поскольку возобновление — более общий механизм, чем завершение, именно его и следует принять, даже если есть сомнения в его полезности; а есть важные случаи, когда подпрограмма оказывается блокированной из-за отсутствия ресурса (например, памяти или гибкого диска в накопителе).
В такой ситуации семантика возобновления позволила бы подпрограмме возбудить исключение, дождаться, пока обработчик найдет ресурс, а затем продолжить выполнение, как если бы ресурс был доступен в самом начале. А вот как выглядели наиболее популярные и убедительные аргументы в пользу завершения: о семантика завершения намного проще семантики возобновления.
Фактически для возобновления нужны базовые механизмы продолжения и вложенных функций, но никакой другой пользы из этих механизмов не извлекается; БПИИИВИВ Обработка исключений о методика восстановления в случае нехватки ресурсов, предлагаемая во втором доводе в пользу возобновления, неудачна в принципе. Она порождает тесные связи между библиотечным и пользовательским кодами, которые трудны для понимания и могут приводить к ошибкам; а во многих предметных областях написаны весьма крупные системы, в которых пригненялась именно семантика завершения, так что возобновление просто необязательно.
Последний довод был подкреплен еще и теоретическим рассуждением Флавия Кристиана (Г!ау)п СПзбап), который доказал, что при наличии завершения семантика возобновления не нужна 1Сг)збап, 19891. Потратив лва года на споры, я вынес впечатление, что можно привести убедительные логические доводы в пользу любой точки зрения. Они имелись даже в специальной работе [Соог1епоп8Ь, 1975] по обработке исключений. Мы оказались в положении все тех же античных философов, которые так яростно н умно спорили о природе Вселенной, что как-то забыли о ее изучении.
Поэтому я просил любого, кто имел реальный опыт работы с большими системами, представить конкретные факты. На стороне семантики возобновления оказался Мартин О'Риордан, сообщивший, что якомпаггия М1сгозой накопила многолетний позитивный опыт применения обработки исключений с возобновлением работыь. Но у О'Риордана не было конкретных примеров; также весьма сомнительно, что первая версия операционной системы 05/2 по-настоящему ценна. Опыт применения ловушек ОН в языке Р1/1 приводился в качестве как аргумента язаь, так и аргумента «против» семантики возобновления. Затем, на заседании в Пало Альта в ноябре 1991 г.