Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 233
Текст из файла (страница 233)
Избежать этого можно так: 1етр!а1е<с!авв Т, с!аяя А> иес1ог< Т, А>й иесгог<Т, А>сорега1ог=(сопя! иесгогй а) // предлагает сильную ( //гарантию(у" Д2) оес1ог 1етр(а); // копирование а втар<иес1ог Ьаве<Т,А»(*1Ь!я, 1етр); //обменместами представлений ге1игл '1Ь!я; Однако реализация авар () по умолчанию не удовлетворяет нашим требованиям для оес1ог базе, т. к. она копирует и уничтожает пес1ог Ьаяе. Следовательно, предостав- ляем специализацию: 1етр1а1е< с!авв Т> оо!с(вгоар(оесгог Ьаяе< Т>й а, иес1ог Ьаяей Ь) ( яиэар(а.а, Ь.а), ясвар(а.о, Ь.о); ятар(а.враге, Ь.врасе); втор(а.!ав1, Ь.!ая1); Старые элементы уничтожаются деструктором 1етр, а память, использованная для их хранения, освобождается деструктором пес1ог Ьаяе объекта 1етр. Производительность этих двух версий должна быть эквивзлентна.
По существу они лишь демонстрируют два способа определить один и тот же набор операций, но вторая 1етр!а! е<с!авя Т, с!аяв А> иес1ог<Т, А>гаесгоэ(солв1 иес1ог Т,А>й а) : иес1ог Ьияе(а, а.я!ее()) ( ип!л!Вайяей сору(а.Ьеу!и(), а.елд(), и); иесгог Ьаве Т, А> Ь(а!!ос, а.я!ее()! ип!и!1!а!!еей сору(а.Ьеу!л(), а.епй(), Ь.о); с!еяггоу е!етепгя() а!!осл!еа!!оса!с(и !ая1 — о); оес1ог Ьавесорегагог=(Ь! Ью=д; ге1игл *1Ьив // предлагает сильную //гарантию !у Л.2) // получение памяти // копирование элементов 1029 Д.З. Безопасные при исключениях методы реализации реализация короче и не копирует код родственных векторных функций, поэтому написанное таким образом присваивание менее подвержено ошибкам и проще сопровождается. Обратите внимание на отсутствие традиционной проверки на присваивание самому себе Я 10АА): Ц (Й!я == ба) ге!игл *Й!з; Приведенные выше реализации присваивания сначала создают копии, а затем меняют местами представления<При этом присваивание самому себе происходит, очевидно, правильно.
Я решил, что выигрыш, полученный от проверки в редком случае присваивания самому себе, с запасом перекрывается потерями в обшем случае, когда присваивается другой оес1ог. В обоих примерах упущены две возможности значительной оптимизации: (1] если емкость выходного вектора достаточно велика, чтобы вместить входной оес1ог, нет нужды выделять новую память; !2] присваивание элементу может дать выигрыш по сравнению с уничтожением и последуюшим созданием элемента. Оптимизируя код, получим: 1етр1а1е<с1аяя Т, с1аяя А> оес1ог< Т, А>й иес1ог< Т, А >зорега1ог=(сопя1 оес1огтх а) // оптимизированная версич, //основная гарантия !Яду) !)" (сарае/1у()< а я!ге!)) ( //распределение нового векторного предсгпавления.
оес1ог 1етр(а), // копирование а яшар< оессог Ьаяе<Т А»(*Йиь 1етр);," ,обльен ллестальи представлений ге!ига "1бив (Х(1Ь/я == йа) ге1игп *1Ь!я; //защита от присввпвиния салюму себе (у !0.4.4) // присвиивоние стирал< элементам: я/ге 1уре яг = я!ге(); я/ге 1уре аяг = а.я!ге(); а!!ос = а.уе1 а!!оса!ос() // копирование распределителя памяпги 1/(аяг <= яг) ( сору(а.беу!п(), а. Ьеу/п() е аяг, о); /ог (Т' р = о+яг; р!= ярасе, л-ьр) р — ' -Т(); // унив нюх<ение лишних //элементов !$ !04.!!) е)зе ( сору(а.
бери(), а. Ьеугп() + яг, о); ип!и!1!а1!гед сору(а.беу1п()+ яг, а.ела~, эрисе);// создание дополншпельннх // зяементов ярасе = о+ аяг, гейьго '1Ь1я; Эти оптимизапии не бесплатны. Алгоритм сору() Я 18.6.1) не предлагает сильной гарантии безопасности исключений. Он не гарантирует, что оставит свой выходной объект неизменным, если в ходе копирования сгенерируется исключение.
Таким образом, если во время работы сору() присваивание Тсорега1ог=(! сгенерирует исклю- Приложение Д. Безопасность исключений и стандартная библиотека 1030 ченне, выходной оесГог не обязательно будет копией входного и не обязательно останется неизмененным. Например, первые пять элементов могут оказаться копиями элементов присваиваемого вектора, а остальные могут остаться неизмененными. Также вероятно, что тот элемент, который копировался, когда Тсорега1ог=() сгенерировал исключение, окажется со значением, не являющимся ни старым значением, ни копией соответствующего элемента присваиваемого вектора И все же, если ТсорегаГог=() при геллерацлли исключения оставляет свои операнды в действительном состоянии, то и песГог останется в действительном состоянии — пусть и не в том, на которое мы надеялись.
В примере я скопировал распределитель памяти с помощью присваивания. На самом деле не требуется, чтобы каждый распределитель памяти поддерживал присваивание Я 19А,З); см. также ф Д.8(9). Присваивание для песГог из стандартной библиотеки предлагает более слабую безопасность исключений, соответствующую последней обсуждаемой реализации,— а заодно и потенциальный выигрыш в производительности. Таким образом присванванне песГог обеспечивает основную гарантию, что отвечает взглядам большинства людей на безопасности исключений. Однако оно не обеспечивает сильную гарантило Я Д.2). Если вы нуждаетесь в присваивании, которое при генерации исключения оставляет песГог неизмененным, следует либо воспользоваться библиотечной реализацией, обеспечивающей сильную гарантию, либо предоставить собственную операцию присваивания.
Например: 1етр1а1ечс1азз Т, с1азз А> оои1 заХе азз1уп ( оесГокТА>й а, сопз1 иесго -ТА>ь Ь) // "очевидное' а = Ь ( иес1ог Т,А> 1етр(аде1 а11оса1о>Л)) 1етр гезегсе(Ь.з1ее()) ,/ог(гурепате иес1ог Т,А>л11егаГогр = Ь.Ьеп1п(); р! Ь.еплГ();++р) 1етр.ризЬ Ьасу(*р); стар(а, 1етр); При нехватке памяти для создания Гетр размером в Ь.я1зе() элементов сгенерируется зЫиЬасГ а!1ос, причем до того, как изменигся а. Точно так же, если рияй Ьасй() терпит неудачу по любой причине, а останется нетронутым, потому что мы применяем ризй Ьасй() к 1етр, а не к а.
В этом случае элементы 1етр, созданные функцией риз й Ьасй(), будут уничтожены до повторной генерации исключения, вызвавшего сбой, Перестановка яшар() не копирует элементы песГог. Она просто меняет местами члены данных оесГог, то есть меняет местами подобъекты песГог Ьазе. Поэтому она не генерирует исключений, даже если операции с элементами могли бы Я ДА,З). Таким образом, за~е азя(дп() не делает побочных копий элементов и разумно эффективна. Как это часто бывает, для очевидной реализации имеются альтернативы. Пусть библиотека займется копированием во временную переменную: 1етр!аГечс1азз Т, с1азз А> иоиГ за/е азз1уп(оесгокТ, А>8 а, сопз1 оес1окТ, А>й Ь) // простое а = Ь ( оес1огч Т, А > Гетр(Ь); // копирование элеллентов Ь во временную перелленную стар(а, 1етр); Д.З.
Безопасные при исключениях методы реализации На самом деле проще воспользоваться вызовом по значенизо Я 7.2): 1етр1а1е<с1аяз Т, с1азз А> иои1 яа/е аязгуп(иес1ог<ТА>й и, иесгог<Т А> Ь) //простое а = Ь ( // (заметьте: Ь передается по значению) яшар(а, Ь), Последние два варианта яа~е акя(дп() не копируют распределитель памяти иес1ог. Это — допустимая оптимизация; см. 2 19.4.3. Д.3.4. рив)1 )зас(с() С точки зрения безопасности исключений, рияй Ьасй() в одном отношении подобна присваиванию: мы должны позаботиться о неизменности иес1ог в случае неудачного добавления нового элемента; 1етр!а1е<с1аяя Тс1азя А> иоиуиесгот Т,А>:ризЬ Ьасй(сопя1 Т/' х) У (красе == 1ая1) ( // нет свободного пространства; перераспределение: иес1ог базе Ь(а1!ос, я!хе() 3 2*я/ее() .
2), // удвоить размер итпй(а!мед сору(и, красе, Ь и), пеш(Ь ярасе) Т(х); // поместить копию х в 'Ьлрасе !2" 10.4.! !) -н-б.красе; дез1гоу е1етепгз(). зшир<иес1ог Ьаяе<Т,А»(Ь, *1Ь/з) // поменять местами представления ге1игп; пети(красе) 2(х); // поместить копию х в *красе !У 10 4. !1) »»красе; Разумеется копирующий конструктор, использованный для инициализации 'красе, может сгенерировать исключение. Если зто случится, значение иес1ог останется неизменным, а красе не увеличится.
Причем элементы иве!огне перемещались в памяти, так что ссылающиеся па них итераторы остаются действительными. Таким образом, данная реализация обеспечивает сильную гарантию, а исключение, сгенерированное распределителем памяти или даже предоставленным пользователем копирующим конструктором, оставит иес1ог неизменным. Стандартная библиотека предлагает именно такую гарантию лля рикЬ Ьасй() Я Д.4.1). Обратите внимание на отсутствие 1гу-блока (кроме скрытого в ишш11а!!вес( сору()). За счет тщательного упорядочения операций гарантируется, что при генерации исключ чения иес1ог останется неизменным.
Обеспечение безопасности исключений путем упорялочения и следования метолике «выделение ресурса есть инициализация» (9 14 А) зачастую оказывается изящнее и произволительнее, чем явная обработка ошибок с использованием 1 у-блоков. Из-за того что программист неудачно упорядочивает код, возникает больше проблем с безопасностью исключений, чем от недостатка особого кода обработки исключений. Основнос правило упорядочения — не уничтожать информацию до того, как Приложение Д, Безопасность исключений и стандартная библиотека 1032 будет создана походящая замена, пригодная для присваивания без риска возникновения исключений.
Исключения создают почву для неожиданностей по частя управления последовательностью выполнения инструкций. Во фрагментах кода с простым локальным управлением, где преобладают, например, орега1ог=((, зауе азз(ул(] и ризй баса(], возможности для сюрпризов ограничены. Довольно просто взглянуть на такой код и спросить себя: «Может ли эта строка программы сгенерировать исключение, и что произойдет, если она это сделает?». Для больших функций со сложным управлением, где имеются вложенные циклы и сложные условные инструкции, подобный прогноз сделать трудно.
Добавление 1гу-блоков увеличивает сложность управления и потому может стать источником недоразумений н ошибок (з 14А). Я полагаю, что упорядочение и методика «выделение ресурса есть инициализация» обладают преимуществом по сравнению с широким использованием 1гу-блоков: упрошение управления. Простой, стильный код проще и понять, и сделать правильным. Реализация иес1ог приведена здесь в качестве примера как трудностей, которые мог)п создать исключения, так н методов преодоления этих трудностей.