Вообще говоря, этот пример работает — если вас устраивает, что вновь вставленные элементы следуют в порядке, обратном порядку соответствующих элементов data. Вставка производится в позиции d. begin(), поэтому последний вставленный элемент попадает в начало контейнера!

Если изменение порядка не было предусмотрено (признайтесь, ведь не было!), проблему можно решить следующим образом:

deque:: iterator insertLocaton = d.begin(); // Сохранить итератор

// для начальной

// позиции d

for (size_t =0;i

d.insert(insertLocaton++,data[i]+41); // в позиции insertLocation

}// и увеличить insertLocation.

// Программа также содержит ошибку!

На первый взгляд кажется, что этот фрагмент решает сразу две проблемы — программа не только наращивает итератор, задающий позицию вставки, но и избавляется от необходимости заново вычислять begin при каждой итерации; тем самым решается второстепенная проблема повторяющихся вычислений, о которой говорилось выше. К сожалению, вместо этих двух проблем возникает третья — программа вообще перестает работать. При каждом вызове deque::insert все итераторы deque, включая insertLocation, становятся недействительными, поэтому второй и все последующие вызовы insert приводят к непредсказуемым последствиям.

После обнаружения этой проблемы (возможно, при помощи отладочного режима STL — см. совет 50) приходит в голову следующее решение:

deque::iterator insertLocation = d.begin():// См. ранее

for (size_t i=0;i

insertLocaton= // итератор insertLocation

d.insert(insertLocaton,data[i]+41); // при каждом вызове insert

++insertLocation; // и увеличивает его.

}

Программа делает именно то, что требовалось, но подумайте, как много времени понадобилось, чтобы прийти к верному решению! А теперь сравните со следующим вызовом transform:

transform(data,data+numDoubles,// Копирование всех элементов

inserter(d,d.begin()),// из data в начало d

bind2nd(plus(),41)); // с прибавлением 41

Возможно, вам потребуется пара минут на анализ конструкции bnd2nd(plus (),41), но после этого все хлопоты с итераторами сводятся к простому заданию начала и конца исходного интервала и вызову inserter при определении начала приемного интервала (см. совет 30). На практике итераторы исходного и приемного интервала обычно вычисляются относительно просто — во всяком случае, это значительно проще, чем диагностика случайного появления недействительных итераторов в теле цикла.

Данный пример убедительно показывает, что программирование циклов часто бывает связано с трудностями. Программисту приходится постоянно следить за тем, чтобы итераторы в процессе цикла не стали недействительными или с ними не были выполнены недопустимые операции. Другой пример скрытого перехода итераторов в недействительное состояние приведен при описании циклических вызовов erase в совете 9.

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

Итак, я объяснил, почему алгоритмы обычно работают эффективнее «ручных» циклов и почему при работе с циклами возникают многочисленные трудности, отсутствующие при использовании алгоритмов. Если мне повезло, вы поверили в силу алгоритмов, но везение — вещь ненадежная, а я хочу окончательно разобраться в этом вопросе перед тем, как следовать дальше. Мы переходим к следующему фактору: наглядности кода. В долгосрочной перспективе принцип наглядности очень важен, поскольку наглядную программу проще понять, она проще усовершенствуется, сопровождается и адаптируется в соответствии с новыми требованиями. Циклические конструкции выглядят привычнее, но алгоритмы обладают значительными преимуществами.

Перейти на страницу:

Все книги серии Библиотека программиста

Похожие книги