Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 140
Текст из файла (страница 140)
Итераторы с проверкой 669 (уре((е5' Сйесйей пег<<опт гес(ог<(п(>, гес(ог<(п(>:: сопл( ((ега(ог> С1С( С1СрЗ = тайе сйесйе(((гс, гс. Ьее(п () +3) ( С1Ср4 = тайе сйесйей(гс) ( сопл( гес(ог<(п(>й и = г( С1Ср5 = тайе сйесйей(г, гг.йее(п () ) ) По умолчанию константные контейнеры имеют константные итераторы, из-за чего и их Сйесйе(1 !(ее тоже константные.
Итератор р5демонстрирует один из способов получения константного итератора из неконстонтного контейнера. Это обьясняет, почему Сйесйе(1 !(ег нуждается в двух параметрах шаблона: один — для типа контейнера, другой — для выбора из альтернативы константный/неконстантный. Имена типов для Сйесйе(1 !(ег весьма длинны и запутаны, но это не имеет значения, когда итераторы используются в качестве аргументов стандартных алгоритмов. Например: (етрйие<с(ат 1(е«> гоЫ тузог( (1(егфгз(, 1(ег 1ат) гоЫ !'(«ес(ог<(п(>а с) (гу ( тузог((тайе сйесйе(((с), тайе сйесйей(с, с.епй() ) ) са(сй (ои( о5' Ьоипйз) ( сегг « "оорз: Ьие !п тузог(() ',и"; аЬог(()( ) Первоначальная версия этого алгоритма заставляла меня предположить, что тут весьма вероятны ошибки выхода за границу диапазона, так что применение здесь итераторов с проверкой явно имеет практический смысл. В реализации Сйесйе(1 !(ег имеем указатель на контейнер и итератор, привязанный к контейнеру: (етр1а(е<с!азз Соп(, с!ат 1(ег = (урепате Соп(:: 1(ега(ог> с1ат Сйесйе(( 1(ег: риЫ(с нега(ог (га!«<1(ег> ( 1(ег сиге; литератор для текущей позиции Соп(* с( дуказатель на текущий контейнер ): Наследование от 1(ега(ог «а1(з позволяет сразу иметь определения необходимых типов.
Очевидная альтернатива — наследование от !(ега(ог, излишне многословна (как в случае гегегзе !(ега(ог из в) 9.2.5). Но никакого требования, чтобы итераторы обязательно были классами, нет, а в случае классов нет требования, чтобы они наследовали от !(ега(ог. Глава 19. Итераторы и аллокаторы Операции класса СЬес1<ей 1<ег довольно просты: <етр!а1е<с!аяя Сои<, с!аяя 1Ьег = <урепате Сои<:: 1<его<о«> с!аяя СЬесйей 1<ег: риЫ!с <<его<о« 1«а!<я<1<ег> ( // ... 1г!епй Ьоо1 орега<ог== (сопт СЬесаей !<егь 1, сои<а СЬесйей !<ега 1) ( ге<и«и <. с==<.с аа !.сиге==!. сип < //Нет умолчательного ини«навигатора. СЬесйей «ег(Сои<а х, 1<егр) < с(ьх), сшт(р) [га!<й(р) <) СЬесйей !<ее(Сои<а х) < с(ах), сиге(х.Ьерп() ) () ге!в«енсе орега<ог' () сопя< ( (1(сигг==с->епй() ) <Ьгов ои< о! Ьоипйя() < «е<игп *си<т; ) ро<п1ег орега<ог-> () сопя< ( !Ясигг==с->епй() ) <Ьгов ои< о!' Ьоипйя (); ге<игп а *сигт СЬесЬей !<ее орега<ог+ (йруегепсе <уре й) сопя< // только для итераторов // произвольного доступа ( <7(с->епй() -сигг<й ) [ й<- (сиге-с->Ьеа[п () ) ге<игл СЬесйей 1<ег (с, сигг+й); ) ) <Ьгов ои< о!' Ьоипйя() геГегепсе прего<о«[) (й<Яегепсе 1уре й) сопя< // толька длл итераторов // произвольного доступа ( (Г(с->еий() -си<т<=й [ ) й<- (сиге-с->Ьер!п () ге<игп сигг(й) < ) ) ) <Ьгон ои< о1 Ьоипйя() СЬесЬей <<егь орега<ог+ч- () ( (Г[сигг == с->епй() ) <Ьгов ои< о/" Ьоипйя (); + вси<т< ге1игп *<Ь<я< //префикснь<й а+ риЫ<с: юо!й га!<й [1<ег р) солт ( (1 ( с- >епй ( ) = = р ) ге<ига < <ог(1<ег рр = с->Ьерп() < рр! =с->епй() < вчрр) </(рр == р) ге<ига< <Ьгов ои< о! Ьоипйя (); ) 671 ) 9.3.
Итераторы с проверкой // лостфиксный л-+ Саесаед нег орегагог" я (тг) ( Саесйеа (гег зтр = *йзтп +е*й(тп ге(игл ппр; ) //проверяется префиксным Еь //префиксный-- СЬесйед 11егз орегагог-- () ( (((сиге == с->Ьеяш () ) гагат ои< о/' ЬоипИи () — сип; геилл *йи; СЬесйез1 11ег орегагог-- (тз) ( Саесйей 11ег гтр = *ганг --*гЬ(з; гегиги Оир; ) // постфиксный —— //проверяется префиксным-- д((/егепсе гуре!иИех() солж (гезигп сиге-с->Ьед1л (); )//только произвольный доступ 1гег ипсаесаеа' ( ) соил( ( гейги сипи ) / л-, -, <, и т.д.
Д!9.б(6Я ): зо(йг(11зз<з(плд>з 1з) ( ЬМ свинг = Оз ( Саесаеа Иег<пзг<зггтд» р (1з,(з.Ьея(п () ) г тЬ11е ( згие] ( зяр; е зсоипг; ) сапа (оиг ог Ьоипдз) ( сои(«яогеггии а/гег "«соил1« "гпез~~л" / //рано или поздно достигнем конца Овес)гез( 11ег может быть инициализирован только для конкретного итератора конкретного контейнера. В более полноценной версии для функции за1Ы() должна быть предоставлена более эффективная реализация, работающая с итераторами произвольного доступа (519.6[6)). После инициализации СЬесйез1 11ег при любых изменениях позиции итератора производится проверка, не выходит ли он при этом за границы диапазона элементов контейнера. Попытка заставить такой итератор с проверкой указывать за допустимые границы генерирует исключение оиг о( Ьоипй. Например: Глава ) 9.
Итераторы и аллокаторы 672 (уре((е/ Сйесйе(1 1(ег< С> !(ега(ог( (уре((е7' Сйесйе(( нег<С, С(: сопя( нега(ог> сопя( дега(ог( Вега(огЬее(л() (ге(игп ((ега(ог(*(ЬЬ,С::Ьев(п() ) () ((ега(ог еп((() (ге(иго ((ега(ог(*(Ь(я, С::еп((() ) ( ) соля ((ега(ог Ьея(п ( ) сопя( (ге(иги соим ((ега(ог ( *(Ь(я, С:: Ьеа(п ( ) ); ) сопя( пега(ог еп((() сопя( (ге(игп сопя( вега(ог(*(ЬЬ, С::еи((() ) ( ) (урелате С:: ге~егепсе (уре прего(ог [ ] ((урепате С:: я(йе (уре и ) (ге(игп Сйесйе(( пег<С> (*сЬЬ) [и); ) Ся Ьаяе () (ге(игп *(Ь(я; ) (( получаем базовый контейнер Теперь мы можем написать: Сйесйе((<нес(ог<(п(» яес (10) Сйесйе((<!(я(<((оиЫе» (я(( яой1 7'( ) ( (и((1 = яес[5] ( и(( 11 = яес[ 15] ( У...
туяом(гес. Ьек(и (), тес.еп((() ) ( сору (пес. Ьее(п ( ), яес. еп(((), (я(. Ьее(и () ) ( У ой (( генерируется ои( о1 Ьоип((я Сйесйе(1 Ьег знает контейнер, к которому он привязан. Это позволяет ему отследить почти все случаи, когда итераторы контейнера становятся недействительными в результате выполнения над контейнером некоторых операций (916.3.6, 916.3.8). Для полной защиты от таких случаев потребуется более сложный и дорогостоящий дизайн итератора с проверками (919.6[7)). Обратите внимание, что постфиксный инкремент +я использует промежуточную переменную, а префиксный инкремент — нет.
По этой причине для итераторов +яр предпочтительнее, чем р++. Поскольку Сйесйе(( Ьег содержит указатель на контейнер, его невозможно напрямую применять ко встроенным массивам. При необходимости можно использовать с аггау Я17.5.4). В завершении темы итераторов с проверкой нам требуется найти способ их относительно простого использования. В этом отношении имеются два основных подхода: 1.
Определить контейнер с проверкой, который ведет себя как остальные контейнеры, но предоставляет более ограниченный набор конструкторов, а его Ьея(п() и еп(1() возвращают Сйесйе(1 Ьег, а не обычные итераторы. 2. Определить инициализируемый произвольным контейнером дескриптор, который и осуществляет проверку доступа к своему контейнеру (919.6[8[). Следующий шаблон прикрепляет итераторы с проверкой к контейнеру: (етр1а(е<с1аяя С> с1аяя Сйесйе((: риЫЬ С ( риЫ(с ( ехр((с(( Сйесйе(((я(яе ( и): С(п) ( ) Сйесйе(((): С() ( ) ) 9.3.
Итераторы с проверкой б73 Очевидным образом избыточная функция базе () призвана обеспечивать типичный для дескрипторов интерфейс. Дескрипторы контейнеров обычно не обеспечивают неявного приведения от типа дескриптора к типу контейнера. Если изменяется размер контейнера, все его итераторы, в том числе и итераторы с проверкой, могут стать недействительными. В таких случаях можно заново проинициализировать Сйесйеч! йеж гоЫ е (гес(ог<(пг>а и') ( Сйесйег! йег<гесгог<т!» р (и'); р.. т! ! = р.!Ыех(); Ф! получаем текуи(ую позицию И. гез(се (у00);,Ур становится недействительным р = Сйесйес! йег<гесгог<т!» (г(, г!. Ьеягп () е!); !! восстанавливаем тек~щую позицию Старая (и недействительная) текущая позиция теряется.
Я предоставил функцию !пггех() как средство извлечения индекса, что позволяет восстановить Сйесйеч! !гег, 19.3.1. Исключения, контейнеры и алгоритмы Вы можете возразить, что одновременное применение стандартных алгоритмов и итераторов с проверкой сродни одновременному использованию ремня и подтяжек. Однако опыт показывает, что для некоторых людей и некоторых приложений небольшая доза паранойи оправдана, особенно если в программу вносят изменения разные люди.
Один из способов контроля ошибок времени выполнения состоит во внесении в код специальных проверочных фрагментов. Под самый конец, когда программа отлажена, они удаляются. Такую практику можно сравнить с надеванием спасательного жилета при плавании вдоль берега и избавлении от него при выходе в открытое море. Но и всегда оставлять проверочные фрагменты нереалистично, так как многие из них действительно заметно снижают эффективность выполнения программы. Поэтому неплохо произвести контрольные замеры и посмотреть, что именно заметно тормозит выполнение программы.