Я включил отладку итератора в приложении путем определения
_HAS_ITERATOR_DEBUGGING = 1
Я ожидал это к действительно просто границам проверочного вектора, но у меня есть чувство, что это делает намного больше, чем это. Какие проверки, и т.д. на самом деле выполняются?
Dinkumware STL, между прочим.
Существует ряд операций с итераторами, которые приводят к неопределенному поведению, цель этого триггера - активировать проверки во время выполнения, чтобы предотвратить его появление (используя утверждения).
Проблема
Очевидной операцией является использование недопустимого итератора, но эта недействительность может возникать по разным причинам:
) [начало, конец)
Стандарт определяет мучительные детали для каждого контейнера, какая операция делает недействительным какой итератор.
Есть также несколько менее очевидная причина, о которой люди склонны забывать: смешивание итераторов с разными контейнерами:
std::vector<Animal> cats, dogs;
for_each(cats.begin(), dogs.end(), /**/); // obvious bug
Это относится к более общей проблеме: достоверность диапазонов, передаваемых алгоритмам.
[cats.begin (), dogs.end ())
недействителен (кроме случаев, когда псевдоним для другого) [cats.end (), cats.begin ())
недопустимо (если cats
не пусто ??) Решение
Решение состоит в добавлении информации к итераторам, чтобы их действительность и действительность диапазонов они определены, могут быть утверждены во время выполнения, таким образом предотвращая возникновение неопределенного поведения.
Символ _HAS_ITERATOR_DEBUGGING
служит триггером для этой возможности, потому что, к сожалению, он замедляет работу программы. Теоретически это довольно просто: каждый итератор становится наблюдателем
контейнера, из которого он выпущен, и, таким образом, получает уведомление об изменении.
В Dinkumware это достигается двумя дополнениями:
И это аккуратно решает наши проблемы:
Стоимость
Стоимость высока, но есть ли правильность цена? Мы можем разбить стоимость:
O (NbIterators)
O (NbIterators)
(Примечание что push_back
или insert
не обязательно аннулируют все итераторы, но erase
делает) O (min (last-first , контейнер.end () - first))
Конечно, большинство библиотечных алгоритмов были реализованы для максимальной эффективности, обычно проверка выполняется раз и навсегда в начале алгоритма, затем запускается непроверенная версия. Однако скорость может сильно снизиться, особенно с циклами, написанными от руки:
for (iterator_t it = vec.begin();
it != vec.end(); // Oops
++it)
// body
Мы знаем, что строка Oops - дурной вкус, но здесь все еще хуже: при каждом запуске цикла мы создаем новый итератор затем уничтожает его, что означает выделение и освобождение узла для списка итераторов vec
... Должен ли я подчеркивать стоимость выделения / освобождения памяти в жестком цикле?
Конечно, a for_each
не столкнется с такой проблемой, что является еще одним убедительным аргументом в пользу использования алгоритмов STL вместо кодированных вручную версий.
Согласно http://msdn.microsoft.com/en-us/library/aa985982%28v=VS.80%29.aspx
Стандарт C ++ описывает, какие функции-члены приводят к тому, что итераторы контейнера становятся недействительными. Два примера:
- Удаление элемента из контейнера приводит к тому, что итераторы элемента становятся недействительными.
- Увеличение размера вектора (вставка или вставка) приводит к тому, что итераторы в контейнер вектора становятся недействительными.
Насколько я понимаю:
_HAS_ITERATOR_DEBUGGING во время выполнения отобразит диалоговое окно, чтобы заявить о неправильном использовании итератора, в том числе:
1) Итераторы, используемые в контейнере после удаления элемента
2) Итераторы, используемые в векторах после вызова функции .push() или .insert()