Зачем использовать итераторы вместо индексов массива? - PullRequest
218 голосов
/ 25 сентября 2008

Возьмите следующие две строки кода:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

А это:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Мне сказали, что второй способ предпочтительнее. Почему именно это?

Ответы [ 25 ]

196 голосов
/ 25 сентября 2008

Первая форма эффективна, только если vector.size () - быстрая операция. Это верно для векторов, но не для списков, например. Кроме того, что вы планируете делать в теле цикла? Если вы планируете получить доступ к элементам, как в

T elem = some_vector[i];

тогда вы делаете предположение, что для контейнера определено operator[](std::size_t). Опять же, это верно для вектора, но не для других контейнеров.

Использование итераторов приблизит вас к независимости контейнера. Вы не делаете предположений о возможности произвольного доступа или быстрой size() операции, а только о том, что контейнер имеет возможности итератора.

Вы можете улучшить свой код, используя стандартные алгоритмы. В зависимости от того, чего вы пытаетесь достичь, вы можете выбрать std::for_each(), std::transform() и так далее. Используя стандартный алгоритм, а не явный цикл, вы избегаете повторного изобретения колеса. Ваш код, вероятно, будет более эффективным (если выбран правильный алгоритм), правильным и многократно используемым.

51 голосов
/ 25 сентября 2008

потому что вы не привязываете свой код к конкретной реализации списка some_vector. если вы используете индексы массива, это должна быть какая-то форма массива; если вы используете итераторы, вы можете использовать этот код в любой реализации списка.

48 голосов
/ 25 сентября 2008

Это часть современного процесса идеологической обработки C ++. Итераторы - это единственный способ итерировать большинство контейнеров, поэтому вы можете использовать его даже с векторами, чтобы получить правильное мышление. Серьезно, это единственная причина, по которой я это делаю - я не думаю, что когда-либо заменял вектор на контейнер другого типа.


Ничего себе, это все еще получает отрицание после трех недель. Я думаю, это не стоит того, чтобы быть немного насмешливым.

Я думаю, что индекс массива более читабелен. Он соответствует синтаксису, используемому в других языках, и синтаксису, используемому для старомодных массивов Си. Это также менее многословно. Эффективность должна быть плохой, если ваш компилятор хорош, и вряд ли есть случаи, когда это все равно имеет значение.

Несмотря на это, я все еще часто использую итераторы с векторами. Я считаю, что итератор является важной концепцией, поэтому я продвигаю его, когда могу.

33 голосов
/ 25 сентября 2008

Представьте, что some_vector реализован с использованием связанного списка. Затем запрос элемента в i-м месте требует выполнения операций i для прохождения списка узлов. Теперь, если вы используете итератор, вообще говоря, он приложит все усилия, чтобы быть максимально эффективным (в случае связанного списка, он будет поддерживать указатель на текущий узел и перемещать его в каждой итерации, требуя только одна операция).

Таким образом, он обеспечивает две вещи:

  • Абстракция использования: вы просто хотите перебрать некоторые элементы, вам все равно, как это сделать
  • Performance
26 голосов
/ 25 сентября 2008

Я собираюсь быть здесь защитником дьяволов, а не рекомендовать итераторов. Основная причина, почему весь исходный код, над которым я работал, от разработки приложений для настольных компьютеров до разработки игр, не требовал использования итераторов. Все время, когда они не требовались, и, во-вторых, скрытые предположения, беспорядок кода и ночные кошмары отладки, которые вы получаете с итераторами, делают их ярким примером того, чтобы не использовать его в любых приложениях, которые требуют скорости.

Даже с точки зрения обслуживания они - беспорядок. Это не из-за них, а из-за псевдонимов, которые происходят за сценой. Откуда я знаю, что вы не реализовали свой собственный список виртуальных векторов или массивов, который делает что-то совершенно отличное от стандартов. Знаю ли я, какой тип сейчас используется во время выполнения? Вы перегрузили оператора, у меня не было времени проверить весь ваш исходный код. Черт, я вообще знаю, какую версию STL вы используете?

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

Извините, я не видел и до сих пор не видел никакого смысла в итераторах. Если они абстрагируют от вас список или вектор, тогда как на самом деле вы уже должны знать, с каким вектором или списком вы имеете дело, если вы этого не сделаете, тогда вы просто будете настраивать себя на некоторые отличные сеансы отладки в будущем.

23 голосов
/ 25 сентября 2008

Возможно, вы захотите использовать итератор, если вы собираетесь добавлять / удалять элементы в векторе, пока вы итерируете его.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Если вы используете индексы, вам придется перетасовывать элементы вверх / вниз в массиве для обработки вставок и удалений.

16 голосов
/ 28 сентября 2008

Разделение концернов

Очень приятно отделить код итерации от «основной» задачи цикла. Это почти дизайнерское решение.

Действительно, итерации по индексу связывают вас с реализацией контейнера. Запрашивая у контейнера начальный и конечный итератор, включает код цикла для использования с другими типами контейнеров.

Кроме того, std::for_each вы СКАЖИТЕ коллекции, что делать, вместо ASKing что-то о ее внутренностях

Стандарт 0x вводит замыкания, которые значительно упростят использование этого подхода - взгляните на выразительную силу, например, например. Ruby's [1..6].each { |i| print i; } ...

Производительность

Но, возможно, гораздо более очевидная проблема заключается в том, что использование подхода for_each дает возможность распараллелить итерацию - блоки потоков Intel могут распределять блок кода по числу процессоров в системе. !

Примечание: после обнаружения библиотеки algorithms, особенно foreach, я потратил два или три месяца на написание смехотворно маленьких структур вспомогательных операторов, которые сводят ваших коллег-разработчиков с ума. После этого времени я вернулся к прагматическому подходу - маленькие петлевые тела не заслуживают foreach не более :) 10 * *

Обязательным для прочтения справочником по итераторам является книга "Extended STL" .

У GoF есть небольшой абзац в конце шаблона Iterator, в котором говорится об этом типе итерации; это называется «внутренний итератор». Посмотрите здесь тоже.

14 голосов
/ 25 сентября 2008

Помимо всех других превосходных ответов ... int может быть недостаточно большим для вашего вектора. Вместо этого, если вы хотите использовать индексирование, используйте size_type для вашего контейнера:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}
14 голосов
/ 25 сентября 2008

Потому что это более объектно-ориентированный. если вы выполняете итерацию с индексом, вы предполагаете:

а) что эти объекты упорядочены
б) что эти объекты могут быть получены с помощью индекса
c) что приращение индекса будет поражать каждый элемент
d) что этот индекс начинается с нуля

С итератором вы говорите: «Дайте мне все, чтобы я мог с ним работать», не зная, какова основная реализация. (В Java есть коллекции, к которым нельзя получить доступ через индекс)

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

14 голосов
/ 25 сентября 2008

Еще одна приятная вещь об итераторах заключается в том, что они лучше позволяют вам выражать (и применять) ваши предпочтения const. Этот пример гарантирует, что вы не будете изменять вектор в середине цикла:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...