В чем разница между const_iterator и неконстантным итератором в C ++ STL?

139

В чем разница между a const_iteratorи an, iteratorи где бы вы использовали один над другим?

Konrad
источник
1
Ну, имя const_iterator звучит так, как будто итератор является const, в то время как этот итератор указывает на фактическое значение const.
talekeDskobeDa

Ответы:

124

const_iterators не позволяют вам изменять значения, на которые они указывают, обычные iterators делают.

Как и во всех вещах в C ++, всегда предпочитайте const, если только нет веской причины использовать обычные итераторы (т.е. вы хотите использовать тот факт, что они не constизменяют указанное значение).

Доминик Роджер
источник
8
В идеальном мире это было бы так. Но с C ++ const хорош только тем, кто написал код :(
JaredPar
изменчивый существует по очень хорошей причине. Он редко используется, и, глядя на Поиск кода Google, кажется, что существует достаточный процент допустимых применений. Ключевое слово - очень мощный инструмент оптимизации, и его удаление не улучшит правильность констант ( coughpointerscoughandcoughreferencescough )
coppro
2
Больше похоже на мощный хак. Из всех случаев, когда я когда-либо видел использование ключевого слова 'mutable', все, кроме одного, были точным индикатором того, что код был плохо написан, и изменчивость нужна была как хак для обхода дефектов.
Джон Диблинг
7
Он имеет законное применение, например, кэширование результатов длинных вычислений в классе const. С другой стороны, это почти единственный раз, когда я использовал изменяемый за почти двадцать лет разработки C ++.
Глава Geek
Типичным примером использования const_iterator может служить случай, когда итератор является rvalue
talekeDskobeDa
40

Они должны быть в значительной степени очевидны. Если итератор указывает на элемент типа T, тогда const_iterator указывает на элемент типа 'const T'.

Это в основном эквивалентно типам указателей:

T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator

Константный итератор всегда указывает на тот же элемент, поэтому итератора сам по себе является Const. Но элемент, на который он указывает, не обязательно должен быть константным, поэтому элемент, на который он указывает, можно изменить. Const_iterator - это итератор, который указывает на элемент const, поэтому, хотя сам итератор может быть обновлен (например, увеличен или уменьшен), элемент, на который он указывает, не может быть изменен.

jalf
источник
1
«Константный итератор всегда указывает на один и тот же элемент», это неверно.
Джон Диблинг
2
Как так? Обратите внимание на отсутствующее подчеркивание. Я сравниваю переменную типа const std :: vector <T> :: iterator с std :: vector <T> :: const_iterator. В первом случае сам итератор является константным, поэтому его нельзя изменить, но элемент, на который он ссылается, может быть изменен свободно.
Джалф
4
Ах я вижу. Да, я пропустил недостающее подчеркивание.
Джон Диблинг
3
@JohnDibling Upvoted за объяснение тонкости между const iteraterи const_iterator.
legends2k
8

К сожалению, многие методы для контейнеров STL принимают итераторы вместо const_iterators в качестве параметров. Поэтому, если у вас есть const_iterator , вы не можете сказать «вставить элемент перед элементом, на который указывает этот итератор» (говоря, что, на мой взгляд, такая вещь не является концептуальным нарушением const). В любом случае, если вы хотите сделать это, вам нужно преобразовать его в неконстантный итератор, используя std :: advance () или boost :: next () . Например. boost :: next (container.begin (), std :: distance (container.begin (), the_const_iterator_we_want_to_unconst)) . Если контейнер представляет собой std :: list , то время выполнения этого вызова будет O (n) .

Поэтому универсальное правило добавлять const везде, где это «логично», менее универсально, когда дело касается контейнеров STL.

Однако контейнеры boost принимают const_iterators (например, boost :: unordered_map :: erase ()). Поэтому, когда вы используете буст-контейнеры, вы можете быть «агрессивными». Кстати, кто-нибудь знает, будут ли исправлены контейнеры STL?

Магнус Андермо
источник
1
Это может быть вопросом мнения. В случае vectorи deque, вставка одного элемента делает недействительными все существующие итераторы, что не очень const. Но я понимаю вашу точку зрения. Такие операции защищены контейнером const, а не итераторами. И мне непонятно, почему в стандартном интерфейсе контейнера нет функции преобразования итератора const в nonconst.
Potatoswatter
Вы правы, Potatoswatter, я категорически, это вопрос мнения о контейнерах произвольного доступа и container.begin () + (в любом случае the_const_iterator_we_want_to_unconst - container.begin ()) равен O (1) . Мне также интересно, почему нет функции преобразования для контейнеров без произвольного доступа, но, может быть, есть веская причина? Знаете ли вы, есть ли какая-то причина, по которой функции для контейнеров неслучайного доступа не принимают const_iterators ?
Магнус Андермо
«По моему мнению, такое высказывание не является концептуальным нарушением const», - это очень интересный комментарий, у меня есть следующие мысли по теме. С простыми указателями можно сказать, int const * foo; int * const foo;и int const * const foo;все три являются действительными и полезными, каждый по-своему. std::vector<int> const barдолжен быть таким же, как второй, но, к сожалению, к нему часто относятся как к третьему. Основной причиной проблемы является то, что мы не можем сказать, std::vector<int const> bar;когда означает, что нет никакого способа получить тот же эффект, что и int const *foo;в векторе.
dgnuff
5

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

Нэвин
источник
4

Минимальные выполнимые примеры

Неконстантные итераторы позволяют вам изменять то, на что они указывают:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);

Const итераторы не делают:

const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

Как показано выше, v.begin()он constперегружен и возвращает либо значение, iteratorлибо const_iteratorзависящее от константы переменной контейнера:

Распространенный случай, когда const_iteratorвсплывающее окно - это когда thisиспользуется внутри constметода:

class C {
    public:
        std::vector<int> v;
        void f() const {
            std::vector<int>::const_iterator it = this->v.begin();
        }
        void g(std::vector<int>::const_iterator& it) {}
};

constделает thisconst, что делает this->vconst.

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

Во многом как const и не-const, вы можете легко конвертировать из не-const в const, но не наоборот:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();

// non-const to const.
std::vector<int>::const_iterator cit = it;

// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

// Compile time error: no conversion from const to no-const.
//it = ci1;

Какой из них использовать: аналогично const intvs int: предпочитайте константные итераторы всякий раз, когда вы можете их использовать (когда вам не нужно изменять контейнер с ними), чтобы лучше документировать свое намерение читать без изменений.

Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
0

(как уже говорили другие) const_iterator не позволяет вам изменять элементы, на которые он указывает, это полезно внутри методов класса const. Это также позволяет вам выразить свое намерение.

Трей Джексон
источник
0

Хорошо, позвольте мне сначала объяснить это на очень простом примере, не используя константный итератор. Рассмотрим коллекцию случайных целых чисел "randomData"

    for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;

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

Мистер кодер
источник