Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 238
Текст из файла (страница 238)
5Е.8[!Ц). Или же 1птгт() реализованатаким образом, чтобы удалять некоторые хвостовые элементы с целью быть уверенным в том, что в контейнере не оставлены недействительные элементы. К сожалению, обеспечение сильной гарантии для 1пзегг() в контейнере честог в'отсутствии оговорок про копирующие операции типа элементов непозволительно.
Цена полной зашиты от исключений при перемещении элементов вектора будет весьма значительной в сравнении со случаем предоставления лишь базовой гарантии. Типы элементов, генерирующие исключения в процессе выполнения копирующих операций, не столь уж и редки. Вот примеры из самой стандартной библиотеки — гесгог<зМпдл, гесгог< гесгог< гзопЫе» и тор< згг4пл, 1пгэ. Контейнеры Изг и честог предоставляют одинаковые гарантии при вставке одного или нескольких элементов. Причина состоит в том, что реализация тпзегг() для 11зг и гесгог применяет одну и ту же стратегию вставки как одного, так и нескольких элементов. В то же время гпар предоставляет сильную гарантию для одноэлементного тпзегг(), и лишь .базовую гарантию для многоэлементных 1пзегг () .
Легко реализовать вставку одного элемента в гоар с обеспечением сильной гарантии. Однако типичная реализация многоэлементной вставки в шар состоит в том, чтобы вставлять элементы один за другим, а в этом случае трудно обеспечить сильную гарантию. Проблема заключается в том, что нет простого способа отката предыдущих успешных вставок в момент, когда терпит неудачу очередная вставка. Если мы потребуем сильной гарантии, что либо все элементы успешно вставлены, либо операция ни на что не повлияла, мы можем создать новый контейнер с последующим выполнением зпар(): (етр(ате<с1азз С, с1ат Пег> гоЫ за3е Ьпзегт(Са с, турепате С::сонм йегатог1, 1гег Ьеагп, 11ег еп4) ( С ттр (с.
Ьее(п ( ), 1); У копируем начачьные элементы 1105 Б.4, Гарантии стандартных контейнеров сору (Ьее!и, еп«, <этег<ег(<тр, апр. ет! () ) ); У копируем новый элемент сору(<,с.ел<<(),<пвегтег(апр,<тр.еп«() ) ) ! !!копируем "хвостовые" элементы виар(с,ппр) < ) Как всегда, этот код может повести себя некорректно, если деструктор элемента сгенерирует исключение. В то же время, если исключение генерируется копирую- шими операциями элемента, то аргумент-контейнер остается неизменным. Е.4.3. Функция амар() Подобно копирующим конструкторам и присваиваниям операции э<вар() необходимы лля многих стандартных алгоритмов и часто предоставляются пользователем.
Например, вог<() и маЫе вог<() обычно переупорядочивают элементы с помощью в<вар() . Таким образом, если функция в<вар() генерирует исключение в момент перестановки элементов контейнера, контейнер может так и остаться с непереставленными элементами или с продублированными элементами, а не с парой элементов, поменявшихся местами. Рассмотрим очевидное определение функции в<вар () из стандартной библиотеки (818.6.8); <етр<а<екс<ат Т> го<а' <<вар ( Тв а, Та Ь ) ( Т <тр = а; а=Ь< Ь = пар; ) Ясно, что в<вар () не генерирует исключений, если только этого не делают копирующие операции типа элемента.
За одним незначительным исключением для ассоциативных контейнеров, гарантируется, что функции в<вар () стандартных контейнеров не генерируют исключений. Как правило, обмен контейнерами выполняется путем обмена структурами данных, играющими роль дескрипторов доступа к элементам (813.5, 917.!.3).
Поскольку сами элементы не перемещаются в памяти, то конструкторы и операции присваивания для элементов не вызываются и, соответственно, исключений не генерируют. Кроме того, стандарт гарантирует, что никакая функция в<вар() стандартной библиотеки не портит значений ссылок, указателей и итераторов, ссылающихся на элементы обмениваемых контейнеров. В итоге, остается единственный потенциальный источник исключений: в ассоциативных контейнерах объект сравнения (817.1.4.1) копируется как часть дескриптора.
Его копирующий конструктор и операция присваивания могут генерировать исключение при вызове функций в<вар() для стандартных контейнеров. По счастью, операции копирования объектов сравнения обычно тривиальны и у них нет поводов генерировать исключения. Пользовательские функции в(вар ( ) должны предоставлять те же самые гарантии. Этого несложно добиться, если программист обменивает дескрипторы, а не тупо копирует всю информацию, на которую дескрипторы ссылаются (Э!3.5, 816.3.9, 817.!.3).
Приложение Е Исключения и безопасность стандартной библиотеки 1106 Е.4.4. Инициализация и итераторы Вьщеление памяти под элементы и инициализация этой памяти — основные части любой реализации контейнеров (ЭЕ.3). Поэтому гарантируется, что стандартные алгоритмы для конструирования объектов в неинициализированной памяти— ип(пупайгег! й!!(), ипуп!пайгео' йй и() и ип(пупайгео сору() (819.4.4) — не оставляют после себя никаких сконструированных объектов в случае генерации ими исключений. Таким образом, эти алгоритмы предоставляют сильную гарантию ЦЕ.2). Так как иногда при этом приходится уничтожать элементы, то требование к деструкторам элементов не генерировать исключений является для этих алгоритмов существенным (см.
ЭЕ.8[141). Кроме того, требуется, чтобы итераторы, передаваемые этим алгоритмам в качестве аргументов, вели себя корректно; они сами должны быть действительными, должны ссылаться на действительные последовательности, а их операции (такие как ++, ! = и *) не должны генерировать исключений при работе с действительными итераторами. Итераторы — это типичные примеры объектов, которые свободно копируются стандартными алгоритмами и операциями над стандартными контейнерами. Соответственно, копирующие операции итераторов не должны генерировать исключений, и стандарт гарантирует, что копирующие операции итераторов, возвращаемых стандартными контейнерами, исключений не генерируют.
Например, итератор, возвращаемый посредством оестог<7Ь::Ьей!и(), можно копировать, не опасаясь исключений. В то же время, операции +ч и — для итераторов могут генерировать исключения. К примеру, мггеатйиу'йога!ох (919.2.6) генерирует исключение для указания на ошибку ввода, а итератор с проверкой диапазона может сгенерировать исключение для указания на попытку выхода за границы действительного диапазона 619.3).
Но исключения не могут генерироваться при перемещении итератора от одного элемента последовательности к другому без нарушения определений операций ++ и — для итераторов. Поэтому пп!пупайге(!у1И(), пп!и!па!!тот! й!! и() и ип!пййайгвй сору() предполагают, что операции ++ и — на их параметрах-итераторах исключений не порождают; если все же исключение генерируется, то это может быть следствием передачи под видом итераторов вовсе и не итераторов (в стандартном понимании), или что итерируемая последовательность не является действительной последовательностью.
И вновь подчеркиваем, что стандартные контейнеры не защищают пользователя от порождаемого пользователем неопределенного поведения кода (эЕ.2). Е.4.5. Ссылки на элементы Когда ссылка, указатель или итератор на элемент контейнера передается некоторому коду, этот код может испортить контейнер, повредив его элемент, Например: го(о'у"[сопл! Ха х) ( !0(<Х> (от! ат.ривл Ьасл(х); (иг<Х>:: йегагог ! = (в!.
Ьвя(п ( ) *! = хт У копируем х в йи 11оу Б.4. Гарантии стандартных контейнеров Если х испорчен, деструктор списка возможно будет не способен должным образом уничтожить Ь<. Например: в<гас< Х ( <и<* р< Х() (р = нет 1н« ) -Х() (<!в!егер< ) р... )< гоЫ та11с1оив ( ) ( Хх< х.р = ге!п<егрге< сов<к!и<*> (7) < .<(х) ' ) ~У нортам х !!бомба с часовым механизмом Е.4.6. Предикаты Многие стандартные алгоритмы и многие операции над стандартными контейнерами полагаются на пользовательские предикаты. В частности, все ассоциативные контейнеры нуждаются в предикатах как для поиска, так и для вставки элементов.
Предикат, используемый операцией стандартного контейнера, может генерировать исключения. На этот случай каждая операция стандартного контейнера предоставляет базовую гарантию, а некоторые операции, такие как йиег< ( ) для единственного элемента, обеспечивают сильную гарантию (ВЕ.4.1). Если предикат генерирует исключение во время операции над контейнером, окончательный набор элементов контейнера может оказаться не совсем таким, какой хотел пользователь, Когда исполнение функции 7() завершается, вызывается деструктор типа 1м«Х>, что, в свою очередь, порождает вызов деструктора типа Хдля испорченного значения. Но результат для <1е1е<е р, когда р отличен от нуля и не указывает на действительное значение типа Х не определен и может спровоцировать немедленный крах, или будет испорчена свободная память, так что проблемы проявятся неожиданным образом позже и в иных частях программы.
Возможность возникновения таких проблем не должна останавливать нас от манипулирования элементами контейнеров посредством ссылок или итераторов; это зачастую самый простой и наиболее эффективный способ. Однако будет разумным проявить дополнительную предосторожность в работе со ссылками на элементы контейнеров. Когда целостность контейнера имеет особое значение, будет неплохо предложить менее опытным программистам более безопасную альтернативу. Например, можно предоставить операцию, которая проверяет действительность нового элемента перед его копированием в особо важный контейнер. Естественно, осуществить такую проверку без детальных знаний прикладного типа невозможно. В самом общем случае, порча элемента контейнера может способствовать непредсказуемому поведению последующих операций с контейнером.