Учтите, что в обоих вариантах все принятие решений можно поручить реализации и ограничиться объявлением следующего вида:
hash_set intTable; // Создать хешированное множество int
Чтобы это объявление нормально компилировалось, хэш-таблица должна содержать данные целочисленных типов (например, int), поскольку стандартные хэш-функции обычно ограничиваются целочисленными типами (в реализации SGI стандартные хэш-функции обладают более широкими возможностями; о том, где найти дополнительную информацию, рассказано в совете 50).
Принципы внутреннего устройства реализаций SGI и Dinkumware очень сильно различаются. В реализации SGI использована традиционная схема открытого хэширования с массивом указателей на односвязные списки элементов. В реализации Dinkumware используется двусвязный список. Различие достаточно принципиальное, поскольку оно влияет на категории итераторов, поддерживаемых этими реализациями. Хэшированные контейнеры SGI поддерживают прямые итераторы, что исключает возможность обратного перебора; в них отсутствуют такие функции, как rbegin или rend. Реализация Dinkumware поддерживает двусторонние итераторы, что позволяет осуществлять перебор как в прямом, так и в обратном направлении. С другой стороны, реализация SGI чуть экономнее расходует память.
Какая из этих реализаций лучше подходит для ваших целей? Понятия не имею. Только вы можете ответить на этот вопрос, однако в этом совете я даже не пытался изложить все необходимое для принятия обоснованного решения. Речь идет о другом — вы должны знать, что несмотря на отсутствие хэшированных контейнеров непосредственно в STL, при необходимости можно легко найти STL-совместимые хэшированные контейнеры (с разными интерфейсами, возможностями и особенностями работы). Более того, в свободно распространяемых реализациях SGI и STLport вам за них даже не придется платить.
Итераторы
На первый взгляд итераторы представляются предметом весьма простым. Но стоит присмотреться повнимательнее, и вы заметите, что стандартные контейнеры STL поддерживают четыре разных типа итераторов:
iterator, const_iterator, reverse_iterator
и const_reverse_iterator
. Проходит совсем немного времени, и выясняется, что в некоторых формах insert и erase только один из этих четырех типов принимается контейнером. И здесь начинаются вопросы. Зачем нужны четыре типа итераторов? Существует ли между ними какая-либо связь? Можно ли преобразовать итератор от одного типа к другому? Можно ли смешивать разные типы итераторов при вызове алгоритмов и вспомогательных функций STL? Как эти типы связаны с контейнерами и их функциями?В настоящей главе вы найдете ответы на эти вопросы, а также поближе познакомитесь с разновидностью итераторов, которой обычно не уделяют должного внимания: isreambuf_iterator. Если вам нравится STL, но не устраивает быстродействие istream_iterator при чтении символьных потоков, возможно, isreambuf_ iterator поможет справиться с затруднениями.
Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator
Как известно, каждый стандартный контейнер поддерживает четыре типа итераторов. Для контейнера container тип iterator работает как Т* тогда как const_iterator работает как const Т* (также встречается запись Т const*). При увеличении iterator или const_iterator происходит переход к следующему элементу контейнера в прямом порядке перебора (от начала к концу контейнера). Итераторы reverse_iterator и const_reverse_iterator также работают как Т* и const Т* соответственно, но при увеличении эти итераторы переходят к следующему элементу в обратном порядке перебора (от конца к началу).
Рассмотрим несколько сигнатур insert и erase в контейнере vector:
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, a 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 IntDeque;// Определения типов
typedef IntDeque:iterator Iter;// упрощают работу
typedef IntDeque::const_iterator Constlter; // с контейнерами STL
// и типами итераторов
iter i;
Constlter ci:
// i и ci указывают на элементы // одного контейнера
if (i=ci)...// Сравнить iterator
//c const_iterator
В данном примере происходит обычное сравнение двух итераторов контейнера, подобные сравнения совершаются в STL сплошь и рядом. Просто один объект относится к типу iterator, а другой — к типу const_iterator. Проблем быть не должно — iterator автоматически преобразуется в const_iterator