Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 231
Текст из файла (страница 231)
(зб] «сильная гираятия (зггопяяцагаппее) для ключевых операций»: в дополнение к обеспечению основной гарантии, устанавливается, что операция или достигает цели, или ни на что не влияет. Эта гарантия предоставляется для ключевых библиотечных операций, типа разй бася(), одноэлементных 1пзег1() для 11з1 и иптШа11яед сору() (5 Л.З.1, э Л.4.1).
Приложение Д. Безопасность исключений и стандартная библиотека 1020 )Зв] яГарантия отсутствия исклнгчвний(пойгоччйцагапсее)длянекоторыхопераций»< в дополнение к обеспечению основной гарантии, устанавливается, что некоторые операции не генерируют исключений. Эта гарантия обеспечивается для нескольких простых операций, типа яшар() и рор Ьасй() (з Д.4,1). Как основная, так и сильная гарантии обеспечиваются при условии, что предоставленные пользователем операции (такие как присваивания или функпия перестановки яшар()) не оставляют элементы контейнеров в недействительных состояниях, не создают утечек ресурсов, и что деструкторы не генерируют исключений. Например, рассмотрим два вспомогательных ()запс))е) Я 25.7) класса: !етр1а!е<с1азз Т с!азз Ба~е( Т Тн // р указывает на 7; распределенный с помощью псчв риЫгс Ба/е(): р(песо 7) () -Ба/е() ( Ие1е!е р; ) Барей орега!ог= (сот! Ба5ей а) ( *р= *а р; ге!игп '!Ыз; ) //-.
!етр/а!е<с1азв Т> с1азз ЕУпза/е ( // небрежный и опасный код Т /Л //р указывает на Т риЫ!с: 1/иза~е(T рр): р(рр) ( ) -1/пза/е() ( гТ ()р — >без!гас!!Ые()) !Ьгтв Е(); йе!е!е р; ) // если нельзя уничтожить — генерируея исключение 1/пза~ей орега!ог —. (сопз! !!пядей а) ( р- -7)); // уничтожение старого значения (у 10.4.11) пеи (р) Т(а.р); //создание копии а р в *р Ц !Ов'.11) ге!игп '!Ь!з; //-. ); оо!с!Яоес!от БаХе<Боте !уре»й оу, иес!ог 1/пза/ Боте !уре»й оЬ) ( оу.а)(1) = Ба/в<Боте !уре (! иЬ.а!(1) = !)пза~е<Боте !уре>(пеги Боте !уре); //-. В этом примере создание Ба/е происходит успешно, только если успешно создается Т.
Создание Т может не состояться из-за неудачного распределения памяти (с генерациеи в!!!эбан а1(ос) или нз-за генерации исключения конструктором Т. Но в каждом успе7пно созданном Ба/е, р укажет на успешно созданный Т; если конструктор терпит неудачу, объект Т(а равно и Ба~е) не создается. Точно так же оператор присваивания Т может сгенерировать исключение, заставляя оператор присваивания Ба/е неявно сгенерировать это исключение снова. Затруднений пе возникает при условии, что оператор присваивания Т всегда оставляет свои операнды в хорошем состоянии.
Поэтому Ба)е — корректно ведущий себя класс, и, следовательно, каждая операция стандартной библиотеки с Ба/е будет иметь разумный и хорошо определенный результат. 1021 Д.2. Безопасность исключений С другой стороны, (Упва/е(1 написан небрежно (вернее наоборот, написан тщательно, чтобы показать, как поступать нв надо ). Создание (/пва/е не потерпит неудачу. Напротив, операции с (/пва/е, типа присваивания и уничтожения, оставлены наедине с множеством потенциальных проблем. Оператор присваивания может потерпеть неудачу при генерации исключения в копирующем конструкторе Т, Это оставило бы Т в неопределенном состоянии, потому что старое значение *р уже разрушено, а никакое новое значение его не заменило.
Вообще говоря, результаты подобного поведения непредсказуемы. Деструктор (/авале отражает непродуманную попытку защититься от нежелательного уничтожения объекта; генерация исключения во время обработки исключения вызовет обращение к /егт/па/е1' (з 14.7), тогда как стандартная библиотека требует, чтобы деструктор завершался нормально после уничтожения объекта. Стандартная библиотека не дает — и не может дать— никаких гарантий, если пользователь предоставляет объекты, ведущие себя столь ужасно. С точки зрения обработки исключении, Ва/е и Упва~е отличаются тем, что Ва/е использует свой конструктор, чтобы установить инвариант (э 24.3.7.1), который позволяет реализовать операции класса просто и безопасно.
Если установить инвариант не удается, исключение генерируется прежде, чем появится недействительный объект. (/пва/е, в свою очередь, действует наобум, без содержательного инварианта, а отдельные операции генерируют исключения в отсутствие общей стратегии обработки ошибок. Естественно, это приводит к нарушениям (разумных) предположений стандартной библиотеки относительно поведения типов. Например, (/плат может оставить в контейнере недействительные элементы после генерации исключения в Тпорега/ог-1) а также способен сгенерировать исключение в своем деструкторе. Заметим, что гарантии стандартной библиотеки относительно предоставленных пользователем неблагополучных операций аналогичны гарантиям языка относительно нарушений основной системы типов.
Если основная операция применяется пе в соответствии с ее спецификациями, поведение не определено. Например, если вы генерируете исключение в деструкторе элемента пес/сг, вы имеете не болыпие основания надеяться на разумный резуль~ат, чем когда вы разыменовываете указатель, инициализированный случайным числом: ейеве ВвтЬ ( риЫ1е //- -ВотЬ( ( Ип вт ТгваЫе(1); ); аеетвг<ВвтЬ> Ы/О) // приводит к неопределенному поведению ио1дЯ ( 1п1'р=гет1егрге1 евв1<дп1<>1рапйЦ; // приводит к неопределенному поведению Р=Т; Решительно утверждаем: если вы руководствуетесь основными правилами языка и стандартной библиотеки, библиотека будет вести себя хорошо, даже когда вы генерируете исключения.
Приложение Д. Безопасность исключений и стандартная библиотека 1022 ооЫ!еаЬ)Ьоо! аЬог1) оесеогк1пГ о)10); оес1ог 1п1>* р = пего оес1ог /пг>)10) аи1о ргг оес1ог /п1» д)пегооес1ог<гаг>)10)) // нет ут ечек //возможная утечка памяти // нет утечек Я 14.4.2) сУ)аЬогг) 1Ьгосв Гlр(); //-. де1еге р; После генерации исключения иес1ог с именем и н иес1ог, контролируемый 0, будут правильно уничтожены, а их ресурсы освободятся.
Но пес/ог, на который указывает р, не защищен от исключений и не будет уничтожен. Чтобы сделать зту часть кода безопасной, мы должны или явно удалить р перед генерацией исключения, или удостовериться, что иес1ог принадлежит объекту вроде аи1о р1г Я 14.4.2), который должным образом уничтожит вектор при возникновении исключения. Обратите внимание, что языковые правила частичного создания и уничтожения гарантируют, что исключения, сгенерированные при построении подобъектов и членов, будут правильно обработаны без особых мер со стороны кода стандартной библиотеки Я 14.4.1). Это правило — фундамент всех технических приемов, имеющих дело с исключениями.
Помните также, что память — не единственный ресурс, который может теряться. Открытые файлы, блокировки, сетевые соединения и нити (спгеас)з) — вот только некоторые примеры системных ресурсов, которые функция должна будет освободить или передать какому-либо объекту перед генерацией исключения. Д.З. Безопасные при исключениях методы реализации Как обычно, стандартная библиотека демонстрирует примеры проблем, встречаю- щихся во многих других контекстах, а также широко применяемые решения.
Основ- ные инструментальные средства, доступные для написания безопасного при исклю- чениях кода, это: )1) 1гу-блок Я 8,3.1) и [2) поддержка методики евыделение ресурса есть инициализация» Я 14.4). Общие принципы, которым надо следовать: 11) никогда не теряйте информацию, пока не обеспечите ей замену; и Помимо обеспечения безопасности исключений как таковой, обычно стремятся также избегать утечек ресурсов. То есть операция, которая генерирует исключение.
должна не только оставить свои операнды в хорошо определенном состоянии, но и обеспечить, чтобы каждый захваченный ресурс (в конечном счете) освободился. Например в точке, где сгенерировано исключение, вся распределенная память должна или освобождаться, или принадлежать объекту, который в свою очередь обязан обеспечить корректное освобождение памяти.
Стандартная библиотека гарантирует отсутствие утечек ресурсов при условии, . что предоставленные пользователем операции, вызываемые библиотекой, также избегают утечек ресурсов. Пример: 1023 Д.З. Безопасные при исключениях методы реализации (2) всегда оставляйте объекты в действительном состоянии при генерации или по- вторной генерации исключений. Действуя в таком духе, мы всегда сможем восстановиться после ошибочной ситуации. Практическая трудность следования этим принципам заключается в том, что невинно выглядящие операции (типа <, =, и зог!(1) могут генерировать исключения.
Чтобы знать, чего ожидать от приложения, требуется опыт. Если вы пишете библиотеку, в идеале следует стремиться к сильной гарантии безопасности исключений Я Д.2) и всегда обеспечивать основную гарантию. При написании конкретной программы о безопасности исключений обычно беспокоятся меньше. Например, если я пишу простую программу анализа данных для личного использования, меня вполне устроит аварийное завершение в маловероятном случае исчерпания виртуальной памяти. Тем не менее, правильность и основы безопасности исключений тесно связаны.
Методы обеспечения основ безопасности исключений, такие как определение и проверка инвариантов Я 24.3.7.1), подобны методам, которые полезны, чтобы сделать программу компактной и правильной. Следпвательно, накладные расхолы на обеспечение основ безопасности исключений (основная гарантия; 4 Д.2) — и даже на обеспечение сильной гарантии — могут окаааться минимальными, а то и вовсе незначительными; см.
З Д.8[171. Далее мы рассмотрим реализацию стандартного контейнера иес!ог Я 16.3) и выясним, что требуется для достижения этого идеала, и где мы могли бы довольствоваться менее жесткими требованиями к безопасности. Д.3.1. Простой вектор Типичная реализация иес?ог (э' 16.3) включает дескриптор, содержащий указатели на первый элемент, на элемент, следулощий за последним. и на «элемент», следующий за концом распределенного пространства Я 17.13) (или эквивалентную информацию, представленную как указатель и смещения): пес?ог: Вот объявление иве!ог — упрощенное, чтобы показать только то, что необходимо для обсуждения безопасности исключений и предотвршцения утечек ресурсов: гетр!аге< с!азз Т, с!азз А = а!!оса!ос<Т > с!азз пес!ог ( риЫ!с; ? и, // накала распргделенноп паллятлл ?' зрасе; // конеп последоваплельности злелленлпов, // на«ало свободного пространства, //распределенного для возллолсного расшоренпя ?" !аз!; // канву распределенного пространства А а!!ос, // распределитель полляли и ехр!(ей пес!ог(з!яе !уре п, сопя! ?8 иа! = ?((, сопл?Ай =А(! Приложение Д.