Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 240
Текст из файла (страница 240)
Библиотека будет функционировать корректно до тех пор, пока предоставляемые пользователем операции Б.б. Рекомендации пользователям стандартной библиотеки 1111 соответствуют базовым гарантиям библиотеки (эЕ.2). В частности, исключения, сгенерированные из стандартных контейнерных операций не вызовут утечек памяти и не оставят контейнеры в недействительных состояниях.
В итоге, перед пользователем библиотеки встает вопрос: каким образом определять свои типы, чтобы они не вызывали утечек ресурсов и не допускали неопределенного поведения? Вот основные правила: 1. При модификации объекта не уничтожайте его старое содержимое до того, как новое содержимое полностью сконструировано и может заменить старое содержимое без риска возникновения исключений. Например, см, реализацию гесгог:: орегагог= (), за~е ттззтдп () и гесгог::рязЬ Ьасй () в эЕ.З. 2. Перед генерацией исключения освободите захваченные ресурсы, которые не приналлежат какому-либо другому объекту.
2а. Методика выделение ресурса есть инициализация» (514.4) и языковые правила, гласящие, что частично созданные объекты уничтожаются в той степени, в которой они были созданы (514.4.1), оказывают здесь большую помощь. К примеру, см. (еай() в 5Е.2. 2Ь. Алгоритм илйипа1ггеЫ сору() и его собратья обеспечивают автоматическое освобождение ресурсов в случае невозможности завершить конструирование набора объектов ЯЕ.4.4). 3.
Перед генерацией исключения удостоверьтесь, что все операнды находятся в действительных состояниях. Всегда оставляйте объекты в состоянии, которое позволяет обратиться к ним и уничтожить их, не вызвав неопределенного поведения или генерации исключения из деструктора. К примеру, см. присваивание для гестог из 9Е.3.2. За. Конструкторы имеют такую особенность, что при генерации в них исключений не остается объектов для последующего уничтожения. Соответственно, перед генерацией исключения нам нет необходимости устанавливать инвариант, но есть необходимость в освобождении ресурсов, захваченных в ходе неудавшегося конструирования. ЗЬ.
Деструкторы особенны тем, что сгенерированное в них исключение почти наверняка ведет к нарушению инвариантов и/или вызову гегт!лше ( ) . Следовать этим правилам на практике не так-то легко. Главная причина состоит в том, что исключения иногда появляются оттуда, откуда их совсем не ждут. Хороший пример — мИ:: ЬаИ а11ос. Любая функция, которая прямо или косвенно использует операцию лев или обращается к аллокатору для выделения памяти, может сгенерировать ЬаИ аИос. В некоторых программах мы можем решить эту частную проблему, стараясь не исчерпать память компьютера. Однако для программ, которые должны работать в течение длительных промежутков времени или должны осуществлять ввод данных произвольного объема, нужно быть готовым к обработке самых разнообразных отказов при выделении принципиально ограниченных компьютерных ресурсов, Каждую функцию нужно подозревать на предмет возможности генерации исключений до тех пор, пока твердо не доказано обратное.
Простой способ попробовать избежать неожиданностей состоит в применении контейнеров элементов, которые не генерируют исключений (вроде контейнеров 1112 Приложение Е Исключения и безопасность стандартной библиотеки указателей или контейнеров элементов простых конкретных типов), или связных контейнеров типа !мг, предоставляющих сильную гарантию (ЗЕ.4). Другой подход — положиться в первую очередь на операции вроде ризЬ Ьасй (), которые предоставляют сильную гарантию, что операция либо успешно заканчивается, либо ни на что не влияет (зЕ.2). Однако сами по себе эти подходы недостаточны для предотвращения утечек ресурсов и могут приводить к слишком ограничительным и пессимистическим методам обработки ошибок и восстановления.
Например, яес- гол< Т' > — тривиально безопасный по отношению к исключениям тип в том случае, если операции над типом Т не генерируют исключений. Однако если указуемые объекты нигде не удаляются, исключение из»есгог приведет к утечке ресурсов. Введение вспомогательного класса Над«11е (~25.7), предназначенного для освобождения ресурсов, и применение»еегаг<Нап«11е<Т > вместо»еегог<Т» >, с большой вероятностью улучшит жизнеспособность кода. Если вы пишите новый код, то можно с самого начала принять систематический план, по которому каждый ресурс представляется классом с инвариантом, предоставляющим базовую гарантию ЯЕ.2).
Таким образом идентифицируются наиболее важные объекты системы, для которых можно обеспечить семантику отката (го1!-Ьасй зеталгтсз), то есть сильную гарантию для операций с ними (возможно, при каких-то специфических условиях). Большинство приложений содержат структуры данных и код, которые написаны без учета безопасности исключений. При необходимости такой код можно встраивать в более надежный (с точки зрения безопасности исключений) каркас либо после проверки, что он не генерирует исключений (как это было для функций стандартной библиотеки языка С; ЗЕ.5.5), либо посредством интерфейсных классов, для которых повеление при исключениях и управление ресурсами может быть точно определено.
Проектируя типы, предназначенные для использования в безопасной по отношению к исключениям среде, нужно обращать особое внимание на операции, используемые стандартной библиотекой: конструкторы, деструкторы, присваивания, сравнения, функции июар(), предикаты и операции на итераторах. Наилучшее решение состоит в определении инварианта класса, который устанавливается конструкторами. Иногда нужно проектировать инвариант класса так, чтобы обьект можно бьио поместить в такое состояние, при котором его можно уничтожить, даже если операция потерпела неудачу в самой «неудобной» точке.
В идеале, такое состояние не должно быть артефактом реализации, искусственно введенным ради удобства обработки исключений, а должно естественным образом вытекать из семантики типа (ЬЕ.3.5). Рассматривая безопасность исключений, делайте акцент на определении действительных состояний объектов (инвариантов) и на надлежащем освобождении ресурсов. Из-за этого тем более важно представлять ресурсы классами. Простым примером служит уеегог Ьазе (яЕ.3.2).
Конструкторы таких «ресурсных» классов захватывают низкоуровневые ресурсы (типа «сырой» памяти для яеегог Ьазе) и устанавливают инварианты (вроде надлежащей инициализации указателей в»есгог Ьазе). Деструкторы этих классов неявно освобождают низкоуровневые ресурсы. Правила частичного конструирования (з!4.4.!) и методика «выделение ресурса есть инициализация» (й)4.4) поддерживают такой способ управления ресурсами. 1113 Е7. Советы Хорошо написанный конструктор устанавливает инвариант класса для объекта (924.3.7.1), то есть он присваивает объекту такое значение, которое способствует упрощению написания кода для операций класса и способствует их успешному выполнению. Это подразумевает, что конструкторам часто приходится захватывать ресурсы. Если же это не удается сделать, то конструкторы могут сгенерировать исключение, чтобы мы попытались справиться с проблемой до того, как объект создан.
Такой подход непосредственно поддерживается и языком, и стандартной библиотекой (зЕ.3.5). Требование освободить ресурсы и перевести объект в действительное состояние до того момента, когда будет генерироваться исключение, означает, что бремя обработки исключения распределяется между генерирующей исключение функцией, функциями, расположенными по цепочке вызовов между последней и обработчиком, и самим обработчиком.
Таким образом, если функция генерирует исключение, это не означает, что она никак не связана с его обработкой: все функции, генерирующие и передающие исключение должны освобождать захваченные ими ресурсы и помещать операнды в согласованные состояния. Если они этого не делают, то обработчику исключений скорее всего остается лишь попытаться более-менее изящно завершить работу приложения.
Е.7. Советы 1. Определитесь с тем, какая степень безопасности исключений нужна вам; зЕ.2. 2. Безопасность исключений должна быть составной частью общей стратегии отказоустойчивости; зЕ.2. 3. Предоставляйте базовую гарантию для всех классов (то есть поддерживайте инвариант и не допускайте утечек ресурсов); зЕ.2, вЕ.3.2, вЕ.4. 4. Где это возможно и позволительно, предоставляйте сильную гарантию (то есть чтобы операция либо успешно выполнялась, либо оставляла операнды нетронутыми); вЕ.2, вЕ.З. 5. Не генерируйте исключений в деструкторах; вЕ.2, зЕ.3.2, зЕ.4. 6. Не генерируйте исключений в итераторах, которые осуществляют проход по действительной последовательности; зЕ.4.1, зЕ.4.4.
7. Безопасность исключений требует тщательного изучения отдельных операций; вЕ.З. 8. Проектируйте шаблоны так, чтобы они были прозрачными по отношению к исключениям; вЕ.3.1. 9. В вопросе приобретения ресурсов отдавайте предпочтение конструкторам, а не функциям йиг(); вЕ.3.5. 10.
Определяйте инвариант класса так, чтобы было ясно, какое состояние объектов класса является действительным; вЕ.2, вЕ.6. 11. Убедитесь, что объект всегда можно поместить в действительное состояние без риска генерации исключения; вЕ.3.2, йЕ.6. 1114 Е.в. Упражнения (*1) Перечислите все исключения, которые могут быть сгенерированы в функции Т() из ~Е,1, (*1) Ответьте на вопросы к примеру из 9Е.1.
('1) Определите класс Телгег, который иногда генерирует исключения в таких базовых операциях, как копирующий конструктор. С помощью Телтег проверьте контейнеры вашей реализации стандартной библиотеки. (*1) Найдите ошибку в «неряшливой» версии конструктора геегог (ВЕ.3.1) и напишите программу, приводящую этот конструктор к краху.
Подсказка: сначала реализуйте деструктор для типа гесгог. (*2) Реализуйте простой список, предоставляющий базовую гарантию. Уточните, что список требует от пользователей, чтобы обеспечить указанную гарантию. (*3) Реализуйте простой список, предоставляющий сильную гарантию. Тщательно протестируйте этот список. Как люди могут убедиться в безопасности этого списка? 8. 9. 12. 13. 14.
15. 16. 17. 18. 19. 20. 21. 1. 3. 4. 5. б. Приложение Е Исключения и безопасность стандартной библиотеки Придерживайтесь простых инвариантов; эЕ.3.5. Перед генерацией исключения оставляйте операнды в действительных со- стояниях; вЕ.2, эЕ.6. Избегайте утечек ресурсов; ВЕ.2, ЭЕ.З.!, ВЕ.6. Представляйте ресурсы непосредственно классами; эЕ.3.2, эЕ.6. Помните, что зтг«9з() может быть альтернативой копированию элементов; эЕ.З.З. Там, где возможно, предпочитайте упорядочение кода явному применению ««у-блоков; 5Е.3.4. Не уничтожайте «старую» информацию до того, как заменяющая ее инфор- мация будет надежно подготовлена; вЕ.З.З, вЕ.6. Полагайтесь на методику «выделение ресурса есть инициализация», вЕ.З, вЕ.3.2, вЕ.6.