Книга: Эффективное использование STL
Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator
Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator
Как известно, каждый стандартный контейнер поддерживает четыре типа итераторов. Для контейнера container<T>
тип iterator
работает как T*
тогда как const_iterator
работает как const T*
(также встречается запись T const*
). При увеличении iterator
или const_iterator
происходит переход к следующему элементу контейнера в прямом порядке перебора (от начала к концу контейнера). Итераторы reverse_iterator
и const_reverse_iterator
также работают как T*
и const T*
соответственно, но при увеличении эти итераторы переходят к следующему элементу в обратном порядке перебора (от конца к началу).
Рассмотрим несколько сигнатур insert
и erase
в контейнере vector<T>
:
iterator insert(iterator position, const T& x);
iterator erase(iterator position);
iterator erase(iterator rangeBegin, iterator rangeEnd);
Аналогичные функции имеются у всех стандартных контейнеров, но тип возвращаемого значения определяется типом контейнера. Обратите внимание: перечисленные функции требуют передачу параметров типа iterator
. Не const_iterator
, не reverse_iterator
и не const_reverse_iterator
— только iterator
. Хотя контейнеры поддерживают четыре типа итераторов, один из этих типов обладает привилегиями, отсутствующими у других типов. Тип iterator
занимает особое место.
На следующей диаграмме показаны преобразования, возможные между итераторами разных типов.
Из рисунка следует, что iterator
преобразуется в const_iterator
и reverse_iterator
, а reverse_iterator
— в const_reverse_iterator
. Кроме того, reverse_iterator
преобразуется в iterator
при помощи функции base
типа reverse_iterator
, а const_reverse_iterator
аналогичным образом преобразуется в const_iterator
. Однако из рисунка не видно, что итераторы, полученные при вызове base
, могут оказаться не теми, которые вам нужны. За подробностями обращайтесь к совету 28.
Обратите внимание: не существует пути от const_iterator
к iterator
или от const_reverse_iterator
к reverse_iterator
. Из этого важного обстоятельства следует, что const_iterator
и const_reverse_iterator
могут вызвать затруднения с некоторыми функциями контейнеров. Таким функциям необходим тип iterator
, а из-за отсутствия обратного перехода от const
-итераторов к iterator
первые становятся в целом бесполезными, если вы хотите использовать их для определения позиции вставки или удаления элементов.
Однако не стоит поспешно заключать, что const
-итераторы вообще бесполезны. Это не так. Они прекрасно работают с алгоритмами, поскольку для алгоритмов обычно подходят все типы итераторов, относящиеся к нужной категории. Кроме того, const
-итераторы подходят для многих функций контейнеров. Проблемы возникают лишь с некоторыми формами insert
и erase
.
Обратите внимание на формулировку: const
-итераторы становятся в целом бесполезными, если вы хотите использовать их для определения позиции вставки или удаления элементов. Называть их полностью бесполезными было бы неправильно. Const-итераторы могут принести пользу, если вы найдете способ получения iterator
для const_iterator
или const_reverse_iterator
. Такое возможно часто, но далеко не всегда, причем даже в благоприятном случае решение не очевидно, да и эффективным его не назовешь. В двух словах этот вопрос не изложить, если вас заинтересуют подробности — обращайтесь к совету 27. А пока имеющаяся информация позволяет понять, почему типу iterator
отдается предпочтение перед его const
- и reverse
-аналогами.
• Некоторым версиям insert
и erase
при вызове должен передаваться тип iterator
. Const
- и reverse
-итераторы им не подходят.
• Автоматическое преобразование const
-итератора в iterator
невозможно, а методика получения iterator
на основании const_iterator
(совет 27) применима не всегда, да и эффективность ее не гарантируется.
• Преобразование reverse_iterator
в iterator
может требовать дополнительной регулировки итератора. В совете 28 рассказано, когда и почему возникает такая необходимость.
Из сказанного следует однозначный вывод: если вы хотите работать с контейнерами просто и эффективно и по возможности застраховаться от нетривиальных ошибок, выбирайте iterator
вместо его const
- и reverse
-аналогов.
На практике выбирать обычно приходится между iterator
и const_iterator
. Выбор между iterator
и reverse_iterator
часто происходит помимо вашей воли — все зависит от того, в каком порядке должны перебираться элементы контейнера (в прямом или в обратном). А если после выбора reverse_iterator
потребуется вызвать функцию контейнера, требующую iterator
, вызовите функцию base
(возможно, с предварительной регулировкой смещения — см. совет 28).
При выборе между iterator
и const_iterator
рекомендуется выбирать iterator
даже в том случае, если можно обойтись const_iterator
, а использование iterator
не обусловлено необходимостью вызова функции контейнера. В частности, немало хлопот возникает при сравнениях iterator
с const_iterator
. Думаю, вы согласитесь, что следующий фрагмент выглядит вполне логично:
typedef deque<int> IntDeque; // Определения типов
typedef IntDeque:iterator Iter; // упрощают работу
typedef IntDeque::const_iterator ConstIter; // с контейнерами STL
// и типами итераторов
iter i;
ConstIter ci;
… // i и ci указывают на элементы
// одного контейнера
if (i==ci)… // Сравнить iterator
//c const_iterator
В данном примере происходит обычное сравнение двух итераторов контейнера, подобные сравнения совершаются в STL сплошь и рядом. Просто один объект относится к типу iterator
, а другой — к типу const_iterator
. Проблем быть не должно — iterator
автоматически преобразуется в const_iterator
, и в сравнении участвуют два const_iterator
.
Именно это и происходит в хорошо спроектированных реализациях STL, но в некоторых случаях приведенный фрагмент не компилируется. Причина заключается в том, что такие реализации объявляют operator==
функцией класса const_iterator
вместо внешней функции. Впрочем, вас, вероятно, больше интересуют не корни проблемы, а ее решение, которое заключается в простом изменении порядка итераторов:
if (c==i)… // Обходное решение для тех случаев,
// когда приведенное выше сравнение не работает
Подобные проблемы возникают не только при сравнении, но и вообще при смешанном использовании iterator
и const_iterator
(или reverse_iterator
и const_reverse_iterator
) в одном выражении, например, при попытке вычесть один итератор произвольного доступа из другого:
if (i-ci>=3)… // Если i находится минимум в трех позициях после ci…
ваш (правильный) код будет несправедливо отвергнут компилятором, если итераторы относятся к разным типам. Обходное решение остается прежним (перестановка i
и ci
), но в этом случае приходится учитывать, что i-ci
не заменяется на ci-i
:
if (c+3<=i)… // Обходное решение на случай, если
// предыдущая команда не компилируется
Простейшая страховка от подобных проблем заключается в том, чтобы свести к минимуму использование разнотипных итераторов, а это в свою очередь подсказывает, что вместо const_iterator
следует использовать iterator
. На первый взгляд отказ от const_iterator
только для предотвращения потенциальных недостатков реализации (к тому же имеющих обходное решение) выглядит неоправданным, но с учетом особого статуса iterator
в некоторых функциях контейнеров мы неизбежно приходим к выводу, что итераторы const_iterator
менее практичны, а хлопоты с ними иногда просто не оправдывают затраченных усилий.
- Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator
- Совет 27. Используйте distance и advance для преобразования const_iterator в iterator
- Совет 28. Научитесь использовать функцию base
- Совет 29. Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе
- reverse
- Фокус-группы вместо пудры
- История развития компьютеров (вместо пролога)
- Можно ли избавиться от необходимости использовать двойной щелчок кнопкой мыши при открытии папки?
- Программы: покупать или использовать нелицензионные?
- Какие пароли не стоит использовать?
- Нужно повторно использовать текст, который я уже набирал ранее. Как его скопировать?
- Текстовые форматы и кодировки, или Почему иногда вместо текста я вижу абракадабру?
- Для работы в Интернете мне надо использовать какой-то «прокси». Как это сделать?
- На сайтах вместо текста отображается непонятный набор символов
- Где скачать аватар, чтобы использовать его на форуме или в дневнике?
- Скорость работы винчестера SATA оставляет желать лучшего. Как использовать его на полную мощность?