Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 234
Текст из файла (страница 234)
)< Деструктор типа чес<ог явно вызывает деструктор типа Т для каждого элемента. Это означает, что если деструктор элемента генерирует исключение, то уничтожение объекта чес<ог терпит неудачу. Это просто катастрофа, если все это происходит при раскрутке стека, вызванной исключением, и вызывается функция <егиииа<е() (514.7). В случае обычного уничтожения объекта, генерация исключения в деструкторе приводит к утечке ресурсов и непредсказуемому поведению кола, полагающегося на разумное поведение объектов.
Не существует действенной зашиты от генерации исключений в деструкторах, так что библиотека не дает никаких гарантий при генерации исключений в деструкторах элементов (йЕ.4). Теперь конструктор можно определить весьма просто: <етр1а<е<с1аяз Т, с<ам А> чес<ог<Т,А>:: чес<ог(я<се <уре п, сопи! Тй ча1, сопя< Ай а) : чес<ог Ьаяе<Т, А> (а, и) У выделяем палить для и элементов ( иттйавгей !<11(ч, чьи, ча1) < (< копируем элементы ) Копирующий конструктор отличается лишь использованием пи<и!<!а1!хей сору () вместо ип!п!йа!1гей Я!1 (): <етр!иге<с<от Т, с<от А> чес<ог<Т,А>::честг(соиз1 чес<ог<Т,А>й а) : чес<ог Ьазе<Т,А> (а.авос, а.я<се() ) ( ии!и!<<а<1<ей сору (а. Ьеи<п (), а.
епй и ч) ' ) Заметим, что этот стиль конструктора полагается на фундаментальное правило языка: если исключение генерируется в конструкторе, то уже сконструированные подобъекты (такие как поля данных базового класса) будут должным образом уничтожены (5! 4.4.1). Алгоритм ии!п!па!1гей 1<11 () и его собратья (5е.4.4) предоставляют аналогичную гарантию для частично созданных последовательностей. Е.З.З. Присваиввние Как обычно, присваивание отличается от конструирования (создания) тем, что нужно позаботиться о старом значении.
Рассмотрим прямолинейную реализацию: Б 3. Технологии реализации безопасности при исключениях 1089 гетр!те <сйвз Т, сйт А> чесгог<Т А ь чесгог< Т А >:: орегагог= (соле! игсгогв а),У дает сильную гарантию ТуЕ 2) ( чесгог Ьазе<Т,А> Ь(а.аНос,а.з!ге() ) г Н получаем память ип!пйа!йеа сору (а. Ьебгл (), а.
елгг(), Ь. ч) г У копируем элементы гйвггоу е!степа () г аНос. ЙеаНосаге (ч, 1авг-ч); чгсго~ Ьазе:: орегагог= (Ь) г Ь.ч 4 Нг гемен *гбй; ) У освобождаем старую память Н размещаем новое представление Н предотвращаем освобождение Это присваивание безопасно, но оно повторяет много кола из конструкторов и деструктора. Избежать этого можно следуюшим образом: гетр!а!в<с!от Т, агат А> чесюг<Т А>ь чесгог<Т А>:: орегагог= (солт ге<гога а) Н дает сильную гарантию ТЗЕ 2) ( чесгог гетр (а) г Нкопируем а знар<чесгог Ьаве<Т,А» (*гбй, гетр) г Н обмен представлениями гешгп *гьй; гетрйге<сйхз Т, сйзз А> чесгог< Т, А>ь чесгог<Т, А>:: орегагог= (сопзг чесюгь а) Н дает базовую гарантшо ТЗЕ2) ( (Т(саргюяу() < а.з!ге() ) Нвыделяем память лод новое представление: ( чесгог гетр (а) г Н копируем а зиар<чесгог Ьазе<Т,А» (*гбй, гетр); Н обмениваем представления Старые элементы уничтожаются деструктором переменной гетр, а память, использованная для их хранения, освобождается деструктором класса чесгог базе при вызове деструктора для гетр.
Производительность этих двух версий должна быть примерно одинаковой. По сути дела, это лишь два разных способа определения одних и тех же операций, но вторая версия короче и не реплицирует код из родственных функций типа чесгог, так что ее применение менее подвержено ошибкам и сопровождать ее легче. Обратите внимание на отсутствие традиционной проверки на самоприсваивание (510.4.4). Показанные реализации присваивания сначала создают копию, а затем меняют местами представления. Очевидно, что при этом корректно обрабатывается и случай самоприсваивания. Я решил, что эффективность, достигаемая наличием проверки редкого случая самоприсваивания, не стоит потерь в обшем случае, когда присваивается отличаюшийся чесгог.
В обоих вариантах упущены две возможности значительной оптимизации кода: 1. Если емкость вектора, которому присваивается значение, достаточно велика для того, чтобы вместить все элементы присваиваемого вектора, то нет необходимости в выделении новой памяти. 2. Присваивание элементов может оказаться эффективнее их уничтожения с последующим конструированием. Реализуя указанные оптимизации, получаем: Приложение Е Исключения и безопасность стандартной библиотеки 1090 саэлсп *тяй; ф(гЬЬ == аа) севисп *гяйэ д защита от самоприсваиванил Я(0.4.4) зйе бре эе = зпе ( ); зйе гуреазе =а.зпе() эУ(азг<=т) ( сору (а .
Ьеь4п (), а . ЬеяГп ( ) эазт, «); уос(Т* р = ээавсг р! =эросе; -нр) р-э-ТО; ) е(зе ( сору(а.Ьеа(п(),а.Ьеа(п() эзс, в); ипэп(яа((сед сору(а. Ьея(п () эзс, а.епй(), эросе) ) р присваиваем старые элементы У уничтоксаем лишние эл-ты Я! 0.4.!!) р присваиваем старые элементы Л конструируем дополнит. эл-ты эрисе = в+пес) тети и *гЬ(э; Выполненная оптимизация не бесплатна. Алгоритм сору () (518.6.1) не предоставляет сильной гарантии безопасности при исключениях. Он не гарантирует, что оставит свой целевой объект нетронутым, если в процессе копирования будет сгенерировано исключение. Таким образом, если Т:: орееагое= () сгенерирует исключение в процессе работы сору(), то вектор, которому присваивается значение другого вектора, не обязательно будет равен последнему, или не обязательно останется в исходном состоянии. Например, первые пять элементов окажутся копиями элементов присваиваемого вектора, а остальные останутся неизменными.
Также вполне вероятно, что тот элемент, который копировался тогда, когда Т::орееагое= () сгенерировал исключение, останется со значением, которое и не равно старому значению, и не равно значению копировавшегося элемента. И все же, если Т::орегагое=О оставляет свои операнды при генерации исключения в действительном состоянии, то весгое также будет находиться в действительном состоянии, пусть и не в том, которое мы ожидали. Стандарт не требует, чтобы каждый аллокатор поддерживал присваивание (519.4.3); он также не указывает в точности, когда аллокаторы копируются. Здесь я копирую аллокатор всегда, когда копируются элементы, память под которые была выделена этим аллокатором.
Присваивание для чес(ог из стандартной библиотеки обеспечивает такую же слабую гарантию безопасности исключений вместе с выигрышем в производительности. Присваивание для стандартного типа весгог обеспечивает лишь базовую гарантию безопасности исключений, что отвечает требованиям со стороны большинства пользователей. Еше раз подчеркнем, что весгое не предоставляет при этом сильной гарантии (5Е.2). Если же вам нужно, чтобы в случае присваивания для типа чешот левый операнд оставался в исходном (то есть неизменном) состоянии при возникновении исключения, то вам придется либо воспользоваться такой реализацией стандартной библиотеки, которая предоставляет в этом случае сильную гарантию, либо написать свою собственную версию операции присваивания.
Например: ЕЗ. Технологии реализации безопасности при исключениях 1091 гетр!ага<с(азз Т, с1ат А> иоЫза~е азз!яп(гсс(ог<Т,А>ь а, сапог иесгог<Т,А>ь Ь) ( иссгог<Т,А> (етр (Ь.авг аПоса1ог() ); гетр . гезегге ( Ь. зйе ( ) ); уог(1урепате иес(ог<Т,А>::Ьегагогр = Ь.Ьеа!п () 1 р!=Ь.спа'() ) ++р) 1втр.риза Ьасл(*р) 1 зн ар (а, гетр); ) При нехватке памяти для создания тетр размером в Ь.з11е () элементов сгенерируется исключение вЫ:: Ьа1! а!!ос до того, как будут внесены изменения в и. Точно так же, если ривЬ Ьасй () терпит неудачу по любой причине, вектор а останется нетронутым, потому что мы применяем ризЬ Ьасй () к гетр, а не к а.
В этом случае элементы гетр, созданные функцией ризЬ Ьас11(), будут уничтожены до того как исключение, вызвавшее сбой в работе, будет сгенерировано повторно. Функция виар() не копирует элементы векторов. Она просто меняет местами их поля данных; то есть она обменивает их подобъекты гестог Ьазе 5Е.3.2). Следовательно, она не генерирует исключений, даже если операции на элементах могли бы это делать ЦЕА.З). В итоге, за!с акт!ли() не делает паразитных копий элементов и демонстрирует приличную эффективность. Как это нередко бывает, имеются альтернативы для очевидных реализаций. Мы можем позволить библиотеке выполнять для нас копирование во временную переменную: 1етр(ага<с(ат Т, с1азз А> иоЫза/е азз!ап(иесгог<Т,А>ь а, сопя! ивсзог<Т,А>ь Ь) ( иес(ог<Т,А> (стр(Ь) 1 й копируем элементы Ь в гетр зн ар (а, 1втр) 1 ) На самом деле, можно воспользоваться и передачей параметров по значению (в7.2.): 1стр1агв<с1азз Т, с1ат А> иоЫ за)в азз!Кп (иес(ог<Т,А>ь а, нссгог<Т,А> Ь) ~УЬ передается по значению ( виар (а, Ь); ) Е.3.4.
Метод ров)з Ьас(с() В контексте безопасности исключений, ризЬ Ьасй () аналогична присваиванию в том смысле, что мы должны позаботиться о неизменности гестог в случае неудачи с добавлением нового элемента: гетр)а(в<с(азз Т, с1ат А> гогЫивстг<Т,А>::раза баса(сопзт Ть х) ( зу'(красс == 1аз1) 1!закончилось свободное пространство: Приложение Е Исключения и безопасность стандартной библиотеки 1092 частое Ьаюе Ь (аИос,ютюе() ? 2"иге(): 2); ип!птабгей сору (ч, юрасе, Ь. ч); пеп (Ь.юрасе) Т (х) т »+Ь. юрасе; Фею(гоу е(етепа (); ютар лестог Ьаюе<ТгА» (Ь, *тЬВ); ге(ига; ) У удваиваем размер Р помещаем копию х в *Ь юрасе Ц(ОА!)) Р обмениваем представления Р помещаем копию к в «юрасе Ябмах)) птг (юрасе) Т(х); -и-красе; Разумеется, копирующий конструктор, использованный для инициализации *юрасе, может сгенерировать исключение.
Если это произойдет, значение юестог останется неизменным, а юрасе не увеличится. В этом случае элементы гесгог остались на своих местах в памяти, так что ссылающиеся на них итераторы действительны. Таким образом, эта реализация предоставляет сильную гарантию того, что исключение, сгенерированное аллокатором или даже копирующим конструктором, предоставленным пользователем, оставит честог неизменным. Стандартная библиотека предоставляет именно такую гарантию для риюЬ Ьас)т() (эЕ.4.1).
Обратите внимание на отсутствие ггу-блока (кроме скрытого в ипиипаИ- сеИ сору() ). За счет тщательного упорядочения операций удалось гарантировать неизменность чес(ог при возникновении исключений. Подход, при котором безопасность исключений достигается путем упорядочения кода и следования методике «выделение ресурса есть инициализация» (514.4), часто оказывается более элегантным и эффективным, чем явная обработка ошибок с использованием югу-блоков.