Итераторы были введены в С++ в качестве основного механизма доступа к элементам контейнера. Они должны обеспечивать унифицированный объектно-ориентированный доступ к элементам. Но насколько он безопасен? То есть, защищают ли итераторы от индексного переполнения? Давайте проверим.
Рассмотрим небольшую программу:
#include <vector> #include <stdio.h> int main() { std::vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); std::vector<int>::iterator it = vec.end(); it++; ++it; printf("%d\r\n", *it); it = vec.begin(); it += 5; printf("%d\r\n", *it); return 0; }
В ней умышленно допускается индексное переполнение контейнера с помощью итератора. Посмотрим, как ее обработают различные компиляторы.
GCC, C++ Builder 6 и Embarcadero RAD Studio 10 молча скомпилировали и запустили ее. При этом на экран были выведены сучайные значения (как правило, нули, но не у всех). Никто из них не запустил никакого исключения.
Microsoft Visual Studio 2017 при компиляции отладочной версии приложения выдала ошибку:
По факту здесь сработал assert, который отловил все попытки преполнения. Но работает он только в отладочной версии приложения. Компиляция релизной версии прошла без вопросов. Исключения также не запускаются.
А что по этому поводу говорит стандарт (черновик С++11 №3337). Итераторы описываются в разделе 24. В пункте 24.2.1 говорится:
«Iterators are a generalization of pointers that allows C++ program to work with different data structures (containers) in a uniform manner»
Или в переводе на русский:
«Итераторы — это обобщение указателей, которое позволяет программе на C++ работать с различными структурами данных (контейнерами) в единой манере.»
В том же пункте чуть дальше говорится:
«Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding sequence. These values are called past-the-end values. Values of an iterator i for which the expression *i is defined are called dereferenceable. The library never assumes that past-the-end values are dereferenceable.»
Или по-русски:
«Так же, как обычный указатель на массив, гарантирует, что существует значение указателя, указывающее на последний элемент массива, так и для итератора любого типа существует значение, которое указывает за последний элемент соответствующей последовательности. Эти значения называются конечными (past-the-end) значениями. Значения итератора i, для которых определено выражение *i, называются разыменованными. Библиотека никогда не предполагает, что конечные значения являются разыменованными.»
Как видите, здесь ничего не говорится о защите от индексного переполнения. Забота об этом остается на плечах программиста.
Выводы
Итераторы создавались как унифицированное средство обращения к элементам коллекции произвольного типа. Но они никогда не выступали как средство защиты от индексного переполнения. Как это часто бывает, безопасноть принесена в жертву производительности.