Обычно вы найдете такой код STL:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Но на самом деле у нас есть рекомендация написать это так:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
Если вы беспокоитесь об объеме, добавьте закрывающие фигурные скобки:
{
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
}
Предполагается, что это даст прирост скорости и эффективности, особенно если вы программируете консоли, потому что функция .end () не вызывается на каждой итерации цикла. Я просто считаю улучшение производительности само собой разумеющимся, это звучит разумно, но я не знаю, насколько сильно, и это определенно зависит от типа контейнера и фактической реализации STL. Но, использовав этот стиль уже пару месяцев, я все равно предпочитаю его первому.
Причина в удобочитаемости: строка for аккуратная и аккуратная. С квалификаторами и переменными-членами в реальном производственном коде довольно легко иметь длину В самом деле для строк, если вы используете стиль в первом примере. Вот почему я намеренно сделал в этом примере горизонтальную полосу прокрутки, чтобы вы понимали, о чем я говорю. ;)
С другой стороны, вы внезапно вводите переменные Iter во внешнюю область цикла for. Но тогда, по крайней мере, в среде, в которой я работаю, Iter был бы доступен во внешней области видимости даже в первом примере.
Что вы думаете об этом? Есть ли какие-нибудь преимущества у первого стиля, кроме, возможно, ограничения возможностей Iter?
Первая форма (внутри цикла for) лучше, если итератор не нужен после цикла for. Это ограничивает область действия циклом for.
Я серьезно сомневаюсь, что есть хоть какой-то прирост эффективности. Его также можно сделать более читабельным с помощью typedef.
typedef SomeClass::SomeContainer::iterator MyIter;
for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Я бы порекомендовал более короткие имена ;-)
@Ferrucio: это недопустимый C++ - возможно, вы используете старую версию VC++, которая не соответствовала стандарту, но вам придется исправить это при обновлении
Правда, старые компиляторы могут по-прежнему принимать старый стандарт. С VC++ у вас действительно есть возможность включить / выключить это.
Вызов end () на каждой итерации может повлиять на производительность в зависимости от того, как реализован контейнер.
@steffenj, этот код, который «работает» в вашей среде, не является стандартным C++ (Visual C++ плохо относился к этому до 2008 года)
Это действительно как по старым, так и по новым стандартам. Просто по новому стандарту область действия ограничена циклом for. Согласно старому стандарту итератор был бы виден до конца области, в которой находится цикл for.
Если цикл не добавляет элементы в контейнер, я ожидаю, что большинство компиляторов будут достаточно умны, чтобы вывести вызов end () из цикла. Если вы вставляете элементы, другого выхода нет.
Если вы не вызываете функцию внутри цикла. Компилятор не может поднять вызов end (), если не докажет, что функция не изменяет контейнер (в большинстве случаев это сложно или невозможно).
Вы можете использовать фигурные скобки вокруг инициализации и цикла, если вас беспокоит область видимости. Часто я объявляю итераторы в начале функции и повторно использую их в программе.
Я считаю второй вариант более читаемым, так как в итоге вы не получите одну гигантскую строку. Тем не менее, Ферруччо поднимает хороший вопрос о прицеле.
вы имеете в виду второй вариант, а не первый?
Я согласен с Ферруччо. Некоторые могут предпочесть первый стиль, чтобы вывести вызов end () из цикла.
Я мог бы также добавить, что C++ 0x действительно сделает обе версии намного чище:
for (auto iter = container.begin(); iter != container.end(); ++iter)
{
...
}
auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
...
}
Так даже лучше! Вы можете сказать за (auto iter: container) {}
Тогда весь этот разговор будет спорным. Ура!
Обычно я писал:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
for(...)
Если вы правильно разделите код на строки, встроенная форма будет одинаково удобочитаемой. Кроме того, вы всегда должны делать iterEnd = container.end()
в качестве оптимизации:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
Iter != IterEnd;
++Iter)
{
}
Обновление: исправлен код по совету паэрцебала.
Верно. И C++ 0x представит недавно обновленное ключевое слово auto, которое позволит нам избежать объявления итератора. Обратите внимание, что я предполагаю, что ваш код не будет компилироваться из-за второго объявления SomeClass :: SomeContainer :: iterator. Это должно быть для (m :: iterator i = ..., iEnd = ...
Так или иначе, у меня нет особо твердого мнения, хотя время жизни итератора склонило бы меня к версии с заданной областью действия.
Однако читабельность может быть проблемой; в этом можно помочь, используя typedef, чтобы тип итератора был немного более управляемым:
typedef SomeClass::SomeContainer::iterator sc_iter_t;
for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Не большое улучшение, но небольшое.
Хорошая идея. Я сам использовал это пару раз, но недостаточно недавно, чтобы не забыть прокомментировать это. Это делает код много более читабельным.
У меня нет опыта работы с консолями, но в большинстве современных компиляторов C++ любой вариант оказывается эквивалентным, за исключением вопроса о масштабах. Компилятор Visual Studio практически всегда даже в отладочном коде помещает сравнение условий в неявную временную переменную (обычно регистр). Таким образом, хотя логически кажется, что вызов end () выполняется на каждой итерации, оптимизированный скомпилированный код на самом деле выполняет вызов только один раз, и сравнение - единственное, что выполняется каждый последующий раз в цикле.
На консолях это может быть не так, но вы можете разобрать цикл, чтобы проверить, происходит ли оптимизация. Если это так, то вы можете использовать любой стиль, который вам нравится или является стандартным для вашей организации.
Другой альтернативой является использование макроса foreach, например форсировать foreach:
BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
mangle( item );
}
Я знаю, что в современном C++ макросы не приветствуются, но пока ключевое слово auto не станет широко доступным, это лучший способ получить что-то лаконичное и читабельное, но при этом полностью безопасное и быстрое. Вы можете реализовать свой макрос, используя любой стиль инициализации, обеспечивающий лучшую производительность.
На связанной странице также есть примечание о переопределении BOOST_FOREACH как foreach, чтобы избежать раздражающих заглавных букв.
Это может привести к несвязному коду, но мне также нравится вынести его в отдельную функцию и передать ей оба итератора.
doStuff(coll.begin(), coll.end())
и имеют..
template<typename InIt>
void doStuff(InIt first, InIt last)
{
for (InIt curr = first; curr!= last; ++curr)
{
// Do stuff
}
}
Что нравится:
Что не нравится:
Но однажды у нас будут лямбды!
Нет, задерживать iter.end()
до начала цикла - плохая идея. Если ваш цикл изменяет контейнер, то конечный итератор может стать недействительным. Кроме того, метод end()
гарантированно соответствует O (1).
Преждевременная оптимизация - корень всех зол.
Кроме того, компилятор может быть умнее, чем вы думаете.
Если ваш цикл изменяет контейнер, то увеличивающийся итератор также становится недействительным, поэтому вы, если вы используете этот тип цикла «foreach», можете также предварительно инициализировать end.
Нет, вы всегда можете поддерживать свой итератор счетчика в актуальном состоянии, сохранив возвращенный итератор от недействительных функций. Фактически, это то, что вы должны делать, и сама причина этих возвращаемых значений. Но в мире нет причин предварительно сохранять конечный итератор.
Посмотрев на это в g ++ при оптимизации -O2 (чтобы быть конкретным)
Нет никакой разницы в сгенерированном коде для std :: vector, std :: list и std :: map (и друзей). Есть крошечные накладные расходы с std :: deque.
В общем, с точки зрения производительности это не имеет большого значения.
Я вообще не считаю это плохим стилем. Просто используйте typedef, чтобы избежать многословия STL и длинных строк.
typedef set<Apple> AppleSet;
typedef AppleSet::iterator AppleIter;
AppleSet apples;
for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
...
}
Спартанское программирование - один из способов смягчить ваши проблемы со стилем.
не обязательно, в моей среде это является законным и действительным: "for (int i = 0; i <max; ++ i) {} newax = i;"