Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 232
Текст из файла (страница 232)
Эта гарантия дается для небольшого числа простых операций типа з)вар() или рор Ьасй() (вЕ 4.1). Как базовая, так и сильная гарантии обеспечиваются при условии, что предоставляемые пользователем операции (такие как операции присваивания и функции зтеар () ) не оставляют элементы контейнера в недействительных состояниях, не допускают утечек ресурсов и что деструкторы не генерируют исключений. Например, рассмотрим два класса, имеющих характер дескрипторных классов (825.7): Е2.
Исключения и безопасность 1081 В этом примере создание Яа/е выполняется успешно, только если успешно создается Т. Создание Тможет не состояться из-за неудачи с вьшелением памяти (с генерацией зМ:: ЬаФ айос) или в случае генерации исключения конструктором Т. Но в каждом удачно созданном ЯаУе поле р указывает на успешно созданный Т, если же конструктор потерпит неудачу, то Т(а равно и й~Ге) не создается. Точно так же операция присваивания Тможет сгенерировать исключение, заставляя операцию присваивания Яауе повторно сгенерировать это исключение. Проблем не возникает, если операция присваивания у типа Т всегда оставляет свои операнды в действительном состоянии.
Поэтому ЯаУе — корректно ведущий себя класс и, следовательно, каждая операция стандартной библиотеки над ЯаУе будет иметь разумный и хорошо определенный результат. С другой стороны, Иаи(Те написан небрежно (точнее, написан тщательно с целью продемонстрировать, как поступать не нужно).
Конструирование Игза/е никогда не потерпит неудачи, а операции с ИгзаУе, вроде операций присваивания и уничтожения, оставлены наедине со множеством потенциальных проблем. Операция присваивания может потерпеть неудачу при генерации исключения в копирующем конструкторе Т. Это оставит Тв неопределенном состоянии, поскольку старое значение "р бьшо уничтожено, а новое значение его не заменило. В общем случае результат непредсказуем. Деструктор Икя(Те демонстрирует непродуманную попытку защититься от нежелательного уничтожения объекта: генерация исключения в процессе обработки исключения приведет к вызову гееайааге() 614.7), в то время как стандартная библиотека требует нормального возврата из деструктора после уничтожения объекта.
Стандартная библиотека не дает,— и не может дать — никаких гарантий, если пользователь предоставляет объекты, которые ведут себя столь плохо. С точки зрения обработки исключений, типы ЯаУе и Иья(Те различаются тем, что .Я4е использует свой конструктор для установки инварианта (З24.3.7.1), позволяющего реализовать операции просто и эффективно. Если установить инвариант не удается, то генерируется исключение до того, как будет сконструирован недействительный объект.
А тип Икя(Те действует наобум без какого-либо значимого инварианта, его операции генерируют исключения без привязки к какой-либо единой стратегии обработки ошибок. Естественно, это приводит к нарушению предположений (разумных) стандартной библиотеки относительно поведения типов. Например, Исяг/е может оставить в контейнере недействительные элементы после генерации исключения из Т:: орегагог= (), а также может сгенерировать исключение в своем деструкторе. Отметим, что гарантии стандартной библиотеки касательно предоставляемых пользователем неблагополучных операций аналогичны гарантиям языка касательно нарушений базовой системы типов. Если базовая операция применяется не в соответствии с ее спецификациями, то результирующее поведение не определено.
Например, если в деструкторе элемента контейнера чес(ог генерируется исключение, то имеется не больше оснований надеяться на разумный результат, чем в случае разыменования указателя, инициализированного случайным числом: с1азз ВотЬ риЫ)с: У.,. -ВотЬ() (глгои ТгоиЫе(); ) Приложение Е. Исключения и безопасность стандартной библиотеки 1082 гес<ог<ВотЬ> Ь (10) Р приводит к неопределенному поведению гоЫ !'() ( !и<* р = гет<егргв< сав«т<*> (гоп<<() ) < Уприводит к неопределенному поведению *р=7 ) Можно твердо заявить, что если вы соблюдаете основные правила языка и стандартной библиотеки, то библиотечные средства будут вести себя корректно, даже когда вы генерируете исключения.
Кроме достижения безопасности исключений как таковой еще стремятся избежать и утечек ресурсов, то есть операция, генерирующая исключение, не только должна оставить свои операнды в действительном состоянии, но и должна обеспечить (в конечном счете) освобождение захваченных ею ресурсов. Например, в точке генерации исключения выделенная ранее память должна либо освобождаться, либо находиться под управлением объекта, который должен нести ответственность за ее дальнейшее освобождение. Стандартная библиотека гарантирует отсутствие утечек ресурсов при условии, что предоставляемые пользователем операции, вызываемые библиотекой, сами не порождают никаких утечек. Рассмотрим пример: го!0 !еаЬ (Ьоо! аЬог<) гес<ог«п<> г(10) < гес<ог<!п<>* р = пе<г гас<о<<!и<> (10) < аи<о р<г<гес<ог<!п<» 0 (пе<г гес<ог<т<> (10) ) < <1 (авог<) <Ьгон 13р ( ) < ,Р де!е<е р; ) 77 нет утечек гу возможны утечки памяти 77 нет утечек (014.4.
1! Здесь при генерации исключения вектор о и вектор, находящийся под управлением 0, будут корректно уничтожены так, что их ресурсы освободятся. В то же время, вектор, на который указывает р, не защищен от исключений и не будет уничтожен. Чтобы сделать эту часть кода безопасной, мы должны либо явным образом вызвать операцию <(е1е<е для указателя р перед генерацией исключения, либо гарантировать, что вектор находится под управлением объекта — такого как апяз р<г (514.4.2) — который сам позаботится о надлежащем уничтожении вектора при возникновении исключения. Отметим, что языковые правила создания и освобождения объектов по частям гарантируют, что исключения, генерируемые в момент создания подобъектов или членов, будут корректно обработаны без специальных мер со стороны кода стандартной библиотеки (514.4.1).
Это правило лежит в основе всех технологий обработки исключений. Помните также, что память не является единственным ресурсом, подверженным утечке. Открытые файлы, блокировки, сетевые соединения и потоки управления (<Ьгеа<!з) — вот примеры системных ресурсов, которые функция перед генерацией исключения должна либо освободить, либо передать некоторому объекту для последующего их освобождения.
ЕЗ. Технологии реализации безопасности при исключениях 1083 Е.З. Технологии реализации безопасности при исключениях Е.З.1. Простой вектор Типичная реализация типа честог использует представление, содержащее указатели на первый элемент, на элемент, следующий за последним элементом, и на область памяти за выделенным под элементы блоком памяти 517.1.3) (возможно эквивалентное представление с указателем и смещениями): 11 гм зрасе !ам частое, Как всегда, стандартная библиотека демонстрирует примеры проблем, которые могут встречаться во многих других контекстах, и широко применимые методы их преодоления. Для написания безопасного кода имеются следующие базисные приемы и методы: 1.
пу-блок (э8.3.1). 2. Технология «выделение ресурса есть инициализация» (5!4,4) 3. Не терять информацию, пока ей не найдена замена. 4. Оставлять объекты в действительном состоянии при генерации (или повторной генерации) исключений. Действуя указанным образом, мы всегда сможем восстановиться после возникновения ошибочных ситуаций. Практическая трудность в следовании данным рекомендациям заключается в том, что невинно выглядящие операции (вроде <, = и затт()) могут генерировать исключения. Требуется опыт, чтобы точно знать, что можно ожидать от приложения. Если вы пишите библиотеку, то в идеале нужно стремиться к сильной гарантии безопасности исключений (5Е.2), безусловно реализуя базовую гарантию.
При написании обычных программ о безопасности исключений обычно заботятся в меньшей степени. Например, если я пишу простую программу анализа данных для личного пользования, меня вполне устроит ее аварийное завершение в маловероятном случае исчерпания виртуальной памяти. Однако отметим, что безопасность программы при возникновении исключений и общая ее корректность тесно связаны между собой. Технологии и методы обеспечения безопасности при исключениях, такие как определение и проверка инвариантов (524.3.7.1), аналогичны методам, которые делают программу компактной и керректной. Отсюда вытекает, что накладные расходы на обеспечение безопасности исключений (базовая гарантти; 5Е.2), даже в случае сильной гарантии, могут оказаться минимальными или даже совсем пренебрежимыми; см.
ЭЕ.8[17]. Ниже мы рассмотрим реализацию стандартного контейнера честог (516.3) и выясним, что требуется для достижения этого идеала, и где можно обойтись менее жесткими требованиями к безопасности. Приложение Е. Исключения и безопасность стандартной библиотеки 1084 Вот упрощенное объявление типа гес<ог, призванное показать лишь то, что имеет отношение к обсуждению безопасности при исключениях и к предотвращению утечек ресурсов: <етр1а<е<е!азз Т, с!азз А = апоса<ог<Т» с!ат гес<ог ( рг(вате: Т" тч Т* красе; Т* !а<а< А аПос< У начало выделенной памяти П конец последовательности эл-ов (начало свобод.
прост-ва) // конец выделенной памяти // аллокатор риЬПс< екрдс1< вес<о<(з1<е <уре и, сопл< Та га1 = Т(), соп<аАа = А () ) гее<ог (сопл< гестаса а) т // копирующий конструктор вес<ага орега<ог= (солта ге<лога а) < //присваивание -гес<ог(); з1<е 0<ре з(зе() сопл< (ге<игл <расе-г< ) т<е <уре сарае!<у() соли (ге<игл 1ам-г< ) воЫризЬ Ьасй(сопл< Та) / У ... Сначала рассмотрим бесхитростную реализацию конструктора: <етр1а<е<е!азз Т, с!азз А> гее<ог<Т,А> «иее<ог(з(<е <уре и, сопл< Та га1, сопл<Аз а) : аПос (а) /У копируем аллокатор г = аПос.апаса<в(п) < /У выделяем пал<ять под эл-ты (т19.4.!) юрасе = 1аз< = г.ьп < Тог(Т* р=г, р! =1ат; +-'р) а.солз<гис<(р,га!) < Псоэдаем копию га( в *р 6194 !) ) Здесь имеются три источника исключений: 1.