Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 233
Текст из файла (страница 233)
Функция аПоса<е () генерирует исключение, индицирующее отсутствие доступной памяти, 2. Копирующий конструктор аллокатора генерирует исключение. 3. Копирующий конструктор элемента типа Т генерирует исключение, если не может скопировать га1 Во всех случаях никакого объекта не создается, так что деструктор класса чес(ог не вызывается (5(4.4.1). Когда функция аПоса<е() терпит неудачу, она завершает работу по оператору <Ьго<г прежде, чем будут выделены какие-либо ресурсы, так что все в порядке. Когда копирующий конструктор типа Ттерпит неудачу, мы уже располагаем выделенной памятью, которую нужно во избежание утечек освободить.
Более серьезная проблема состоит в том, что этот копирующий конструктор может сгенерировать исключение после корректного создания некоторого количества элементов, но не всех элементов. Е 3. Технологии реализации безопасности при исключениях 1085 Чтобы справиться с проблемой в этом случае, мы могли бы отслеживать, какие элементы были созданы, и уничтожить их (и только их) в случае ошибки: юетр1аюе<с1аюю Т, с(аюю А> весюог<Т,А>::гесюог(ю1ье «уре л, солю! Ть га1, солю!Аь а) : айос(а) У копируем алло катар в = а!!ос. а1!оса!в (л); УУ выделяем память лод эл-ты 1!его!ог р; ( «егтог епИ = г»пт )ОГ(рг иг р! =ЕПФ; »»р) а!!ОС. СОПЮ)Гиег (р, Гаг); !аюю = юрасе = р; ) саюсЬ (... ) )ог(«ега(ого = и! 9! =р! ««9) а!!осАеююгоу(9) т а!!ос.т!еа!!оса!в (г, л); Йголо ) ) УУ конструируем элемент !а !9 4.
!) УУ уничтожаем созданные элементы УУ освобождаем помять УУУловторно генерируем исключение !етр!а!в<с!аюю гог, с)аюю Т> коЫ итп«т11ье4 /И (гог Ьеа, гог епИ, солю! Ть х) ( гог р! югу ( )ог(р=Ьед! р! =епН; ««р) пет (ююаю(с саюю<иоЫ*> (ь*р) ) Т(х); ) саюсЬ (... ) ( )ог(гоге = Ьедг 9!=р; ««д) (ь*а)->-Т(); УУю!0.4 !! Йгонч ) ) УУ создаем колит х в *р !Ю!04.!!) Любопытная конструкция ь+р «заботится» об итераторах, которые не являются указателями.
В этом случае мы берем адрес разыменованного элемента с целью получения указателя. Явное приведение к типу юоЫ гарантирует, что используется стандартная библиотечная размещающая функция (519.4.5), а не какая-либо поль- Здесь накладные расходы связаны лишь с югу-блоком. В хорошей реализации С++ эти накладные расходы пренебрежимо малы по сравнению со стоимостью выделения памяти и инициализации элементов. Для реализаций, где организация ггу-блока накладна, заслуживает внимание проверка юу(п) перед входом в югу-блок и отдельная обработка случая пустого вектора. Основную работу для этого конструктора выполняет реализация ип«и«а!ю'- ге«( й!!(), безопасная в контексте исключений: 1086 Приложение Е Исключения и безопасность стандартной библиотеки зовательская функция орегаюг лел () для Т".
Этот код оперирует на довольно низком уровне, где написание по настоящему общего кода затруднено. По счастью, нам нет нужды повторно реализовывать ил!а!ИаИгез! ИИ(), потому что стандартная библиотека обеспечивает для нее желаемую сильную гарантию (зЕ.2). Часто важно, чтобы инициализируюшая операция либо завершалась успешно, проинициализировав все элементы, либо она завершалась неудачей, не оставляя за собой никаких созданных элементов. Поэтому алгоритмы ал!л!йаИгез! ЯИ(), ал!л!ИаИгег! !тИ л () и ил(л!паИгег! сору () (819.4.4) стандартной библиотеки обеспечивают такую сильную гарантию безопасности исключений (5Е.4.4).
Обратите внимание на то, что алгоритм ил!л!ИаИгез! ИИ() не зашишает от исключений, которые генерируются деструкторами элементов или итераторными операциями (5Е.4.4). Такая защита была бы слишком дорогой (см. 5Е.8[16-17)). Алгоритм ил!л1ИаИгез! ИИ( ) может применяться ко множеству видов последовательностей.
Соответственно, он использует прямой (однонаправленный) итератор (519.2.1) и не может гарантировать уничтожения элементов в порядке, обратном их созданию. Используя ал!л!г!а!(гет! ИИ(), мы можем написать: гетр!иге<с!азз Т, с1азз А> »етог<Т, А>::»естес(з(ге туре л, солзт Та»а1, соля!Аз а) : аИос(а) ( » = аИос.аИосаге(л); тгу ( ил!лЫалгед ЯИ(», »+л, »а1) г У копируем элементы красе = 1ага = 1н-л; сатсл (... ) ( адов.деаИосате(», л) г тягам»; ) ) (т Освобождаем помять У повторно генерируем исютючение Лично я не стал бы вызывать этот код. В следующем разделе будет показано, как его можно существенно упростить.
Обратите внимание на то, что конструктор повторно генерирует перехваченное исключение. Это делается для того, чтобы тип»есгог был прозрачен для исключений и чтобы пользователь мог определить истинную причину исключения. Все контейнеры стандартной библиотеки обладают этим свойством.
Прозрачность по отношению к исключениям — часто наилучшая политика для шаблонов и других «тонких» слоев программного обеспечения. Напротив, главные части системы (модули) должны в обшем случае брать на себя ответственность за обработку исключений. Разработчик модуля должен перечислить все исключения, которые модуль может сгенерировать.
Для этого могут потребоваться группировка исключений (514.2), отображение исключений низкоуровневых процедур в исключения уровня модуля (8!4.6.3) и спецификация исключений (514.6), Е.З. Технологии реализации безопасности при исключениях 1087 Е.З.2. Явное управление памятью Опыт показывает, что написание корректного кода, безопасного в контексте исключений, при помоши явного применения йу-блоков является более трудной задачей, чем это кажется на первый взгляд.
Эти трудности даже не всегда оправданы, ибо имеется альтернатива: методология «выделение ресурса есть инициализация» (514.4) позволяет уменьшить объем кода и сделать его более элегантным. Ключевой ресурс, который требует яесгог — это память, необходимая для хранения его элементов. Если выделить отдельный класс для представления понятия памяти, используемой типом вес!ог, можно значительно упростить результируюший код и уменьшить риск допущения утечек памяти: гетр(те<с(ат Т, с(аяя А = айосаяог<Т» я(гис! чесгог Ьаяе ( А айос; Т* г; Т* ярасе/ Т* 1ат; И аллокатор // начало выделенной памяти // конец последовательности эл-ов (начоло свобод.
прост-ва) И конец выделенной памяти гес(ог Ьаяе(сопя! Аа а, !урепатеА::я(яе !уре п) : айос(а), г(айос.айосаге(п) ), эросе(г«п), 1аяя(г«п) () -гессог Ьаяе () (аИос. Йеайосаяе (г, 1аяя-г) г ) гетр(аге<с1ат Т> го!Иятар(гесяог Ьаяе<Т>а а, гесгог Ьаяе<Т>а Ь) ( ятар(а.айос, Ь.аИос); ятар(а.г, Ь. г); яыар(а.красе, Ь.эросе); ятар (а.(ат, Ь.(аяо г ) Отталкиваясь от гесгог Ьаяе, можно определить гес!ог следуюшим образом: гетр(а(е<с!аяя Т, с(ат А = айосагог<Т» с(аяя гесгог: рыгаге гесяог Ьаяе<Т, А> ( гоЫдея(гоу е!степь() ((ог(Т* р = гт р!=враге; ««р) р->-Т(); ярасеюп ) Пока г и 1ая! корректны, вес!ог Ьаяе может быть уничтожен. Класс гес!ог Ьаяе имеет дело с памятью для типа Т, а не с объектами типа Т.
Следовательно, пользователь юс!ог Ьаяе должен уничтожить все объекты, которые созданы в выделенной вес!ог Ьаяе памяти, прежде чем уничтожить сам гес!ог Ьаяе. Естественно, гесгог Ьаяе написан так, что при генерации исключения (копируюшим конструктором аллокатора или функцией а11осаге()) никакого объекта вес!ог Ьаяе не создается и память не теряется. Нам нужно также уметь обменивать ((о яшар) друг с другом объекты типа гес!ог Ьаяе. Умолчательный впар(), однако, нашим нуждам не удовлетворяет, поскольку он копирует и уничтожает временные объекты, а так как гес!ог Ьаяе— это вспомогательный класс специального назначения, которому не придана семантика зашишенного копирования, то эти уничтожения приведут к нежелательным побочным эффектам. Как следствие, мы предоставляем специализацию: Приложение Е Исключения и безопасность стандартной библиотеки 1088 риЫ!с: ехрйсй чес<ог (я!хе <уре и, сопя< Тй ча< = Т(), соим Ай = А () ) < чес<ог (солей чес<огй а) < У копирующий конструктор вес<ага орега<ог= (сопи вес<ага а) < I< операция присваивания -чес1ог() (йетгоу е!ететя() < ] з!се <уре я!<е() сопя< (ге<игл ярасе-ч< ) з!се <уре сарае<<у() сопя< (ге<ига 1ам-ч< ) чоЫ ризЬ Ьаса(сопя< Тй) У...