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

В Херб Саттерс 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) и так далее.

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

21

Решение

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

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

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

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

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

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

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

25

Другие решения

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

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

или же

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers
10

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

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

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

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

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

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

10
По вопросам рекламы [email protected]