Онлайн библиотека PLAM.RU

Загрузка...



  • Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator
  • Совет 27. Используйте distance и advance для преобразования const_iterator в iterator
  • Совет 28. Научитесь использовать функцию base
  • Совет 29. Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе
  • Итераторы

    На первый взгляд итераторы представляются предметом весьма простым. Но стоит присмотреться повнимательнее, и вы заметите, что стандартные контейнеры STL поддерживают четыре разных типа итераторов:

    iterator, const_iterator, reverse_iterator
    и
    const_reverse_iterator
    . Проходит совсем немного времени, и выясняется, что в некоторых формах insert и erase только один из этих четырех типов принимается контейнером. И здесь начинаются вопросы. Зачем нужны четыре типа итераторов? Существует ли между ними какая-либо связь? Можно ли преобразовать итератор от одного типа к другому? Можно ли смешивать разные типы итераторов при вызове алгоритмов и вспомогательных функций STL? Как эти типы связаны с контейнерами и их функциями?

    В настоящей главе вы найдете ответы на эти вопросы, а также поближе познакомитесь с разновидностью итераторов, которой обычно не уделяют должного внимания:

    istreambuf_iterator
    . Если вам нравится STL, но не устраивает быстродействие
    istream_iterator
    при чтении символьных потоков, возможно,
    istreambuf_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
    менее практичны, а хлопоты с ними иногда просто не оправдывают затраченных усилий.

    Совет 27. Используйте distance и advance для преобразования const_iterator в iterator

    Как было сказано в совете 26, некоторые функции контейнеров, вызываемые с параметрами-итераторами, ограничиваются типом

    iterator
    ;
    const_iterator
    им не подходит. Что же делать, если имеется
    const_iterator
    и вы хотите вставить новый элемент в позицию контейнера, обозначенную этим итератором?
    Const_iterator
    необходимо каким-то образом преобразовать в
    iterator
    , и вы
    должны
    принять в этом активное участие, поскольку, как было показано в совете 26, автоматического преобразования
    const_iterator
    в iterator не существует.

    Я знаю, о чем вы думаете. «Если ничего не помогает, берем кувалду», не так ли? В мире C++ это может означать лишь одно: преобразование типа. Стыдитесь. И где вы набрались таких мыслей?

    Давайте разберемся с вредным заблуждением относительно преобразования типа. Посмотрим, что происходит при преобразовании

    const_iterator
    в
    iterator
    :

    typedef deque<int> IntDeque; // Вспомогательные определения типов

    typedef IntDeque::iterator Iter;

    typedef IntDeque::const_iterator ConstIter;

    ConstIter ci; // ci - const iterator

    Iter i(ci); // Ошибка! He существует автоматического

                // преобразования const_iterator

                // в iterator

    Iter i(const_cast<Iter>(ci)); // Ошибка! Преобразование const_iterator

                                  // в iterator невозможно!

    В приведенном примере используется контейнер

    deque
    , но аналогичный результат будет получен и для
    list, set, muliset, mulimap
    и хэшированных контейнеров, упоминавшихся в совете 25. Возможно, строка с преобразованием будет откомпилирована
    для
    vector и
    string
    , но это особые случаи, которые будут рассмотрены ниже.

    Почему же для этих типов контейнеров преобразование не компилируется? Потому что

    iterator
    и
    const_iterator
    относятся к разным классам, и сходства между ними не больше, чем между
    string
    и
    complex<double>
    . Попытка преобразования одного типа в другой абсолютно бессмысленна, поэтому вызов
    const_cast
    будет отвергнут. Попытки использования
    static_cast
    ,
    reintepreter_cast
    и преобразования в стиле C приведут к тому же результату.

    Впрочем, некомпилируемое преобразование все же может откомпилироваться, если итераторы относятся к контейнеру

    vector
    или
    string
    . Это объясняется тем, что в реализациях данных контейнеров в качестве итераторов обычно используются указатели. В этих реализациях
    vector<T>::iterator
    является определением типа для
    T*, vector<T>::const_iterator
    — для
    const T*
    ,
    string::iterator
    — для
    char*
    , а
    string::const_iterator
    — для
    const char*
    . В реализациях данных контейнеров преобразование
    const_iterator
    в
    iterator
    вызовом
    const_cast
    компилируется и даже правильно работает, поскольку оно преобразует
    const T*
    в
    T*
    . Впрочем, даже в этих реализациях
    reverse_iterator
    и
    const_reverse_iterator
    являются полноценными классами, поэтому
    const_cast
    не позволяет преобразовать
    const_reverse_iterator
    в
    reverse_iterator
    . Кроме того, как объясняется в совете 50, даже реализации, в которых итераторы контейнеров
    vector
    и
    string
    представлены указателями, могут использовать это представление лишь при компиляции окончательной (release) версии. Все перечисленные факторы приводят к мысли, что преобразование
    const
    -итераторов в итераторы не рекомендуется и для контейнеров
    vector
    и
    string
    , поскольку переносимость такого решения будет сомнительной.

    Если у вас имеется доступ к контейнеру, от которого был взят

    const_iterator
    , существует безопасный, переносимый способ получения соответствующего типа
    iterator
    без нарушения системы типов. Ниже приведена основная часть этого решения (возможно, перед компиляцией потребуется внести небольшие изменения):

    typedef deque<int> IntDeque; //См. ранее

    typedef IntDeque::iterator Iter;

    typedef IntDeque::const_iterator ConstIter;

    IntDeque d;

    ConstIter ci;

    …                  // Присвоить ci ссылку на d

    Iter i(d.begin()); // Инициализировать i значением d.begin()

    advance(i, distance(i, ci)); // Переместить i в позицию ci

    Решение выглядит настолько простым и прямолинейным, что это невольно вызывает подозрения. Чтобы получить

    iterator
    , указывающий на тот же элемент контейнера, что и
    const_iterator
    , мы создаем новый
    iterator
    в начале контейнера и перемещаем его вперед до тех пор, пока он не удалится на то же расстояние, что и
    const_iterator
    ! Задачу упрощают шаблоны функций
    advance
    и
    distance
    , объявленные в
    <iterator>
    .
    Distance
    возвращает расстояние между двумя итераторами в одном контейнере, a
    advance
    перемещает итератор на заданное расстояние. Когда итераторы
    i
    и
    ci
    относятся к одному контейнеру, выражение
    advance(i, distance(i, ci))
    переводит их в одну позицию контейнера.

    Все хорошо, если бы этот вариант компилировался… но этого не происходит. Чтобы понять причины, рассмотрим объявление

    distance
    :

    template<typename InputIterator>

    typename iterator_traits<InputIterator>::difference_type

    distance(InputIterator first, InputIterator last);

    Не обращайте внимания на то, что тип возвращаемого значения состоит из 56 символов и содержит упоминания зависимых типов (таких как differenceype). Вместо этого проанализируем использование параметра-типа

    InputIterator
    :

    template<typename InputIterator>

    typename iterator_traits<InputIterator>::difference_type

    distance(InputIterator first, InputIterator last);

    При вызове

    distance
    компилятор должен определить тип, представленный
    InputIterator
    , для чего он анализирует аргументы, переданные при вызове. Еще раз посмотрим на вызов
    distance
    в приведенном выше коде:

    advance(i, distance(i,ci)); // Переместить i в позицию ci

    При вызове передаются два параметра,

    i
    и
    ci
    . Параметр
    i
    относится к типу
    iter
    , который представляет собой определение типа для
    deque<int>::iterator
    . Для компилятора это означает, что
    InputIterator
    при вызове
    distance
    соответствует типу
    deque<int>::iterator
    . Однако
    ci
    относится к типу
    ConstIter
    , который представляет собой определение типа для
    deque<int>::const_iterator
    . Из этого следует, что
    InputIterator
    соответствует типу
    deque<int>::const_iterator
    .
    InputIterator
    никак не может соответствовать двум типам одновременно, поэтому вызов
    distance
    завершается неудачей и каким-нибудь запутанным сообщением об ошибке, из которого можно (или нельзя) понять, что компилятор не смог определить тип
    InputIterator
    .

    Чтобы вызов нормально компилировался, необходимо ликвидировать неоднозначность. Для этого проще всего явно задать параметр-тип, используемый

    distance
    , и избавить компилятор от необходимости определять его самостоятельно:

    advanced.distance<ConstIter>(i, ci)); // Вычислить расстояние между

                                          // i и ci (как двумя const_iterator)

                                          // и переместить i на это расстояние

    Итак, теперь вы знаете, как при помощи

    advance
    и
    distance
    получить
    iterator
    , соответствующий заданному
    const_iterator
    , но до настоящего момента совершенно не рассматривался вопрос, представляющий большой практический интерес: насколько эффективна данная методика? Ответ прост: она эффективна настолько, насколько это позволяют итераторы. Для итераторов произвольного доступа, поддерживаемых контейнерами
    vector
    ,
    string
    ,
    deque
    и т. д., эта операция выполняется с постоянным временем. Для двусторонних итераторов (к этой категории относятся итераторы других стандартных контейнеров, а также некоторых реализаций хэшированных контейнеров — см. совет 25) эта операция выполняется с линейным временем.

    Поскольку получение

    iterator
    , эквивалентного
    const_iterator
    , может потребовать линейного времени, и поскольку это вообще невозможно сделать при недоступности контейнера, к которому относится
    const_iterator
    , проанализируйте архитектурные решения, вследствие которых возникла необходимость получения
    iterator
    по
    const_iterator
    . Результат такого анализа станет дополнительным доводом в пользу совета 26, рекомендующего отдавать предпочтение
    iterator
    перед
    const
    - и
    reverse
    -итераторами.

    Совет 28. Научитесь использовать функцию base

    При вызове функции

    base
    для итератора
    reverse_iterator
    будет получен «соответствующий»
    iterator
    , однако из сказанного совершенно не ясно, что же при этом происходит. В качестве примера рассмотрим следующий фрагмент, который заносит в вектор числа 1–5, устанавливает
    reverse_iterator
    на элемент 3 и инициализирует
    iterator
    функцией
    base
    :

    vector<int> v;

    v.reserve(5); //См. совет 14

    for (int i=1; i<=5; ++i){ // Занести в вектор числа 1-5

     v.push_back(i);

    }

    vector<int>::reverse_iterator ri = // Установить ri на элемент 3

     find(v.rbegin(), v.rend(), 3);

    vector<int>::iterator i(ri.base()); // Присвоить i результат вызова base

                                        // для итератора ri

    После выполнения этого фрагмента ситуация выглядит примерно так:

    На рисунке видно характерное смещение

    reverse_iterator
    и соответствующего базового итератора, воспроизводящего смещение
    begin()
    и
    end()
    по отношению к
    begin()
    и
    end()
    , но найти на нем ответы на некоторые вопросы не удается. В частности, рисунок не объясняет, как использовать
    i
    для выполнения операций, которые должны были выполняться с
    ri
    .

    Как упоминалось в совете 26, некоторые функции контейнеров принимают в качестве параметров-итераторов только

    iterator
    . Поэтому если вы, допустим, захотите вставить новый элемент в позицию, определяемую итератором
    ri
    , сделать это напрямую не удастся; функция
    insert
    контейнера
    vector
    не принимает
    reverse_iterator
    . Аналогичная проблема возникает при удалении элемента, определяемого итератором
    ri
    . Функции erase не соглашаются на
    reverse_iterator
    и принимают только
    iterator
    . Чтобы выполнить удаление или вставку, необходимо преобразовать
    reverse_iterator
    в
    iterator
    при помощи
    base
    , а затем воспользоваться iterator для выполнения нужной операции.

    Допустим, потребовалось вставить в

    v
    новый элемент в позиции, определяемой итератором
    ri
    . Для определенности будем считать, что вставляется число 99. Учитывая, что 
    ri
    на предыдущем рисунке используется для перебора справа налево, а новый элемент вставляется перед позицией итератора, определяющего позицию вставки, можно ожидать, что число 99 окажется перед числом 3 в обратном порядке перебора. Таким образом, после вставки вектор
    v
    будет выглядеть так:

    Конечно, мы не можем использовать 

    ri
    для обозначения позиции вставки, поскольку это не
    iterator
    . Вместо этого необходимо использовать
    i
    . Как упоминалось выше, когда
    ri
    указывает на элемент 3,
    i
    (то есть
    r. base()
    ) указывает на элемент 4. Именно на эту позицию должен указывать итератор
    i
    , чтобы вставленный элемент оказался в той позиции, в которой он бы находился, если бы для вставки можно было использовать итератор
    ri
    . Заключение:

    • чтобы эмулировать вставку в позицию, заданную итератором

    ri
    типа
    reverse_iterator
    , выполните вставку в позицию
    r.base()
    . По отношению к операции вставки
    ri
    и
    r.base()
    эквивалентны, но
    r.base()
    в действительности представляет собой
    iterator
    , соответствующий
    ri
    .

    Рассмотрим операцию удаления элемента. Вернемся к взаимосвязи между

    ri
    и исходным вектором (по состоянию на момент, предшествующий вставке значения 99):

    Для удаления элемента, на который указывает итератор

    ri
    , нельзя просто использовать
    i
    , поскольку этот итератор ссылается на другой элемент. Вместо этого нужно удалить элемент, предшествующий
    i
    . Заключение:

    • чтобы эмулировать удаление в позиции, заданной итератором

    ri
    типа
    reverse_iterator
    , выполните удаление в позиции, предшествующей
    ri.base()
    . По отношению к операции удаления
    ri
    и
    ri.base()
    не эквивалентны, a
    ri.base()
    не является объектом
    iterator
    , соответствующим
    ri
    .

    Однако к коду стоит присмотреться повнимательнее, поскольку вас ждет сюрприз:

    vector<int> v;

    … // См. ранее. В вектор v заносятся

      // числа 1-5

    vector<int>::reverse_iterator ri = // Установить ri на элемент 3

     find(v.rbegin(), v.rend(), 3);

    v.erase(--ri.base()); // Попытка стирания в позиции.

                          // предшествующей ri-base():

                          // для вектора обычно

                          // не компилируется

    Решение выглядит вполне нормально. Выражение

    --ri.base()
    правильно определяет элемент, предшествующий удаляемому. Более того, приведенный фрагмент будет нормально работать для всех стандартных контейнеров, за исключением
    vector
    и
    string
    . Наверное, он бы мог работать и для этих контейнеров, но во многих реализациях
    vector
    и
    string
    он не будет компилироваться. В таких реализациях типы
    iterator
    const_iterator
    ) реализованы в виде встроенных указателей, поэтому результатом вызова
    i.base()
    является указатель. В соответствии с требованиями как C, так и C++ указатели, возвращаемые функциями, не могут модифицироваться, поэтому на таких платформах STL выражения типа
    --i.base()
    не компилируются. Чтобы удалить элемент в позиции, заданной итератором
    reverse_iterator
    , и при этом сохранить переносимость, необходимо избегать модификации возвращаемого значения
    base
    . Впрочем, это несложно. Если мы не можем уменьшить результат вызова
    base
    , значит, нужно увеличить
    reverse_iterator
    и после этого вызвать
    base
    !

    …                       //См. ранее

    v.erase((++ri).base()); // Удалить элемент, на который указывает ri;

                            // команда всегда компилируется

    Такая методика работает во всех стандартных контейнерах и потому считается предпочтительным способом удаления элементов, определяемых итератором

    reverse_iterator
    .

    Вероятно, вы уже поняли: говорить о том, что функция

    base
    класса
    reverse_iterator
    возвращает «соответствующий»
    iterator
    , не совсем правильно. В отношении вставки это действительно так, а в отношении удаления — нет. При преобразовании
    reverse_iterator
    в
    iterator
    важно знать, какие операции будут выполняться с полученным объектом
    iterator
    . Только в этом случае вы сможете определить, подойдет ли он для ваших целей.

    Совет 29. Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе

    Предположим, вы хотите скопировать текстовый файл в объект

    string
    . На первый взгляд следующее решение выглядит вполне разумно:

    ifstream inputFile("interestringData.txt");

    string fileData(istream_iterator<char>(inputFile)), // Прочитать inputFile

     istream iterator<char>());                         // в fileData

    Но вскоре выясняется, что приведенный синтаксис не копирует в строку пропуски (

    whitespace
    ), входящие в файл. Это объясняется тем, что
    istream_iterator
    производит непосредственное чтение функциями
    operator<<
    , а эти функции по умолчанию не читают пропуски.

    Чтобы сохранить пропуски, входящие в файл, достаточно включить режим чтения пропусков сбросом флага

    skipws
    для входного потока:

    ifstream inputFile("interestingData.txt");

    inputFile.unset(ios::skipws); // Включить режим

                                  // чтения пропусков

                                  // в inputFile

    string fileData(istream_iterator<char>(inputFile)), // Прочитать inputFile

     istream_iterator<char>());                         // в fileData.

    Теперь все символы

    InputFile
    копируются в
    fileData
    .

    Кроме того, может выясниться, что копирование происходит не так быстро, как вам хотелось бы. Функции

    operator<<
    , от которых зависит работа
    stream_iterator
    , производят форматный ввод, а это означает, что каждый вызов сопровождается многочисленными служебными операциями. Они должны создать и уничтожить объекты
    sentry
    (специальные объекты потоков ввода-вывода, выполняющие начальные и завершающие операции при каждом вызове
    operator<<
    ); они должны проверить состояние флагов, влияющих на их работу (таких, как
    skpws
    ); они должны выполнить доскональную проверку ошибок чтения, а в случае обнаружения каких-либо проблем — проанализировать маску исключений потока и определить, нужно ли инициировать исключение. Все перечисленные операции действительно важны при форматном вводе, но если ваши потребности ограничиваются чтением следующего символа из входного потока, без них можно обойтись.

    Более эффективное решение основано на использовании неприметного итератора

    istreambuf_iterator
    . Итераторы
    istreambuf_iterator
    работают аналогично
    istream_iterator
    , но если объекты
    istream_iterator<char>
    читают отдельные символы из входного потока оператором
    <<
    , то объекты
    streambuf_iterator
    обращаются прямо к буферу потока и непосредственно читают следующий символ (выражаясь точнее, объект
    streambuf_iterator<char>
    читает следующий символ из входного потока
    s
    вызовом
    s.rdbuf ()->sgetc()
    ).

    Перейти на использование

    istreambuf_iterator
    при чтении файла так просто, что даже программист Visual Basic сделает это со второй попытки:

    ifstream inputFile("interestingData.txt");

    string fileData(istreambuf_iterator<char>(inputFile)),

     istreambuf_iterator<char>0);

    На этот раз сбрасывать флаг skpws не нужно, итераторы

    streambuf_iterator
    никогда не пропускают символы при вводе и просто возвращают следующий символ из буфера.

    По сравнению с

    istream_iterator
    это происходит относительно быстро. В проведенных мною простейших тестах выигрыш по скорости достигал 40%, хотя в вашем случае цифры могут быть другими. Не удивляйтесь, если быстродействие будет расти со временем; итераторы
    istreambuf_iterator
    населяют один из заброшенных уголков STL, и авторы реализаций еще недостаточно позаботились об их оптимизации. Например, в моих примитивных тестах итераторы
    istreambuf_iterator
    одной из реализаций работали всего на 5% быстрее, чем
    istream_iterator
    . В таких реализациях остается широкий простор для оптимизации
    istreambuf_iterator
    .

    Если вы планируете читать из потока по одному символу, не нуждаетесь в средствах форматирования ввода и следите за эффективностью выполняемых операций, три лишних символа на итератор — не такая уж дорогая цена за заметный рост быстродействия. При неформатном посимвольном вводе всегда рассматривайте возможность применения

    sreambuf_iterator
    .

    Раз уж речь зашла о буферизованных итераторах, следует упомянуть и об использовании

    ostreambuf_iterator
    при неформатном посимвольном выводе. По сравнению с
    ostream_iterator
    итераторы
    ostreambuf_iterator
    обладают меньшими затратами (при меньших возможностях), поэтому обычно они превосходят их по эффективности.









    Главная | Контакты | Нашёл ошибку | Прислать материал | Добавить в избранное

    Все материалы представлены для ознакомления и принадлежат их авторам.