Для вектора, почему предпочитаете итератор указателю?

В Херб Саттерс When Is a Container Not a Container? Он показывает пример переноса указателя в контейнер:

  // Example 1: Is this code valid? safe? good?
  //
  vector<char> v;

  // ...

  char* p = &v[0];

  // ... do something with *p ...

Затем следует "улучшение":

  // Example 1(b): An improvement
  //               (when it's possible)
  //
  vector<char> v;

  // ...

  vector<char>::iterator i = v.begin();

  // ... do something with *i ...

Но на самом деле не дает убедительного аргумента:

В целом, неплохо руководствоваться предпочтением использовать итераторы вместо указателей, когда вы хотите указать на объект, который находится внутри контейнера. В конце концов, итераторы становятся недействительными в основном в те же моменты времени и теми же способами, что и указатели, и одна из причин, по которой существуют итераторы, заключается в том, чтобы предоставить способ "указать" на содержащийся объект. Так что, если у вас есть выбор, предпочитайте использовать итераторы в контейнерах.

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

  1. Вы не можете всегда удобно использовать итератор, где вы можете использовать указатель. (См. Пример ниже.)

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

В случае вектора, итератор - это просто RandomAccessIterator. Для всех намерений и целей это тонкая оболочка над указателем. Одна реализация даже признает это:

   // This iterator adapter is 'normal' in the sense that it does not
   // change the semantics of any of the operators of its iterator
   // parameter.  Its primary purpose is to convert an iterator that is
   // not a class, e.g. a pointer, into an iterator that is a class.
   // The _Container parameter exists solely so that different containers
   // using this template can instantiate different types, even if the
   // _Iterator parameter is the same.

Кроме того, реализация хранит значение элемента типа _Iterator, который pointer или же T*, Другими словами, просто указатель. Кроме того, difference_type для такого типа std::ptrdiff_t и определенные операции являются просто тонкими обертками (т.е. operator++ является ++_pointer, operator* является *_pointer) и так далее.

Следуя аргументу Саттера, этот класс итераторов не дает никаких преимуществ по сравнению с указателями, а имеет только недостатки. Я прав?

3 ответа

Для векторов в неуниверсальном коде вы в основном правы.

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

(Эта абстракция, помимо прочего, позволяет реализациям заменять базовую реализацию указателя на что-то более сексуальное, например, проверяемые диапазоном итераторы для использования при отладке.)

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

Итераторы также самодокументированы так, как не указатели. Что делает int* указать на? Без понятия. Что делает std::vector<int>::iterator указать на? Ага…

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

Я согласен, что аргумент Саттера столь же убедителен, как и большинство других его аргументов, то есть не очень.

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

Это не один из недостатков. Иногда слишком "удобно" передавать указатель туда, куда вы действительно не хотели, чтобы он перемещался. Наличие отдельного типа помогает в проверке параметров.

Использованы некоторые ранние реализации T* для вектора:: итератор, но это вызывало различные проблемы, например, люди случайно передавали несвязанный указатель на функции-члены вектора. Или присвоение NULL итератору.

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

Это было написано в 1999 году, когда мы также верили, что код в <algorithm> следует оптимизировать для разных типов контейнеров. Не намного позже все были удивлены, увидев, что составители сами это выяснили. Универсальные алгоритмы с использованием итераторов работали просто отлично!

Для std::vector нет абсолютно никаких временных затрат на использование итератора вместо указателя. Вы узнали, что класс итератора - это просто тонкая оболочка над указателем. Компиляторы также увидят это и сгенерируют эквивалентный код.

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

vector<int>::iterator it; // uninitialized iterator
it++;

или же

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers
Другие вопросы по тегам