Является ли инициализация Итератора внутри для цикла рассмотренным плохим стилем и почему?

Обычно вы найдете код 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. Но, используя этот стиль в течение пары месяцев, я действительно предпочитаю его в любом случае.

Причиной является читаемость: линия для линии аккуратная и аккуратная. С квалификаторами и переменными-членами в реальном коде производства довольно легко иметь действительно для строк, если вы используете стиль в первом примере. Вот почему я намеренно заставил его иметь горизонтальную полосу прокрутки в этом примере, просто чтобы вы поняли, о чем я говорю.;)

С другой стороны, вы внезапно вводите переменные Iter во внешнюю область цикла for. Но тогда, по крайней мере, в среде, в которой я работаю, Iter был бы доступен во внешней области даже в первом примере.

Как вы относитесь к этому? Есть ли какой-либо pro для первого стиля, кроме возможности ограничения объема Iter?

Ответ 1

Если вы правильно привяжете свой код к строкам, встроенная форма будет одинаково читаема. Кроме того, вы всегда должны делать iterEnd = container.end() как оптимизацию:

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
    IterEnd = m_SomeMemberContainerVar.end();
    Iter != IterEnd;
    ++Iter)
{
}

Обновление: исправлен код на каждый paercebal совет.

Ответ 2

Другой альтернативой является использование макроса foreach, например boost foreach:

BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
   mangle( item );
}

Я знаю, что макросы обескуражены в современном С++, но пока ключевое слово auto широко доступно, это лучший способ найти что-то, что является кратким и читаемым, и все еще полностью типичным и быстрым. Вы можете реализовать свой макрос, используя любой стиль инициализации, который обеспечивает лучшую производительность.

Там также есть заметка на связанной странице о переопределении BOOST_FOREACH как foreach, чтобы избежать раздражающих всех шапок.

Ответ 3

Первая форма (внутри цикла for) лучше, если итератор не нужен после цикла for. Он ограничивает область действия циклом for.

Я серьезно сомневаюсь, что есть какой-либо выигрыш в эффективности. Это также можно сделать более читаемым с помощью typedef.

typedef SomeClass::SomeContainer::iterator MyIter;

for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

Я бы порекомендовал более короткие имена, -)

Ответ 4

Нет, это плохая идея, чтобы удерживать iter.end() до начала цикла. Если ваш цикл изменяет контейнер, конечный итератор может быть недействительным. Кроме того, гарантируется, что метод end() будет равен O (1).

Преждевременная оптимизация - это корень всего зла.

Кроме того, компилятор может быть более умным, чем вы думаете.

Ответ 5

Посмотрев на это в g++ при оптимизации -O2 (просто чтобы быть конкретным)

Нет разницы в сгенерированном коде для std::vector, std:: list и std:: map (и друзей). С std:: deque крошечные накладные расходы.

Итак, в общем, с точки зрения производительности это мало чем отличается.

Ответ 6

У меня нет особо сильного мнения так или иначе, хотя время жизни итератора будет наклонять меня к версии с расширенными возможностями.

Однако читаемость может быть проблемой; что может помочь с помощью typedef, поэтому тип итератора немного более управляем:

typedef SomeClass::SomeContainer::iterator sc_iter_t;

for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

Не огромное улучшение, но немного.

Ответ 7

У меня нет никакого опыта в консоли, но в большинстве современных компиляторов С++ любой вариант становится эквивалентным, за исключением вопроса о сфере видимости. Компилятор визуальной студии практически всегда даже в коде отладки помещает сравнение условий в неявную временную переменную (обычно это регистр). Таким образом, хотя логически это выглядит так, что вызов end() выполняется через каждую итерацию, оптимизированный скомпилированный код на самом деле только делает вызов один раз, а сравнение - это единственное, что выполняется каждое время субклиента через цикл.

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

Ответ 8

Это может быть сделано для несвязанного кода, но я также хотел бы вывести его на отдельную функцию и передать ему итераторы.

doStuff(coll.begin(), coll.end())

и имеют..

template<typename InIt>
void doStuff(InIt first, InIt last)
{
   for (InIt curr = first; curr!= last; ++curr)
   {
       // Do stuff
   }
 }

Что нравится:

  • Никогда не упоминайте уродливый тип итератора (или подумайте о том, const или не const)
  • Если на каждой итерации есть выигрыш от не вызывающего end(), я получаю его

Не нравится:

  • Разрывает код
  • Накладные расходы на дополнительный вызов функции.

Но однажды у нас будут лямбды!

Ответ 9

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

typedef set<Apple> AppleSet;
typedef AppleSet::iterator  AppleIter;
AppleSet  apples;

for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
   ...
}

Spartan Programming - один из способов смягчить ваши проблемы стиля.

Ответ 10

Вы можете выставить фигурные скобки вокруг инициализации и цикла, если вас беспокоит область видимости. Часто то, что я буду делать, это объявить итераторы в начале функции и повторно использовать их во всей программе.

Ответ 11

Я нахожу второй вариант более удобочитаемым, так как вы не оказываетесь в одной гигантской линии. Тем не менее, Ферруччио хорошо разбирается в сфере применения.

Ответ 12

Я согласен с Ферруччио. Первый стиль может быть предпочтительным для некоторых, чтобы вывести вызов конца() из цикла.

Я также могу добавить, что С++ 0x действительно сделает обе версии более чистыми:

for (auto iter = container.begin(); iter != container.end(); ++iter)
{
   ...
}

auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
   ...
}

Ответ 13

Я бы обычно писал:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
                                   IterEnd = m_SomeMemberContainerVar.end();

for(...)