Считается ли инициализация итератора внутри цикла циклом плохим стилем и почему? - PullRequest
11 голосов
/ 10 октября 2008

Как правило, вы найдете код 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?

Ответы [ 13 ]

13 голосов
/ 10 октября 2008

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

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

Обновление: исправлен код в соответствии с рекомендациями Паерсебала.

9 голосов
/ 10 октября 2008

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

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

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

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

5 голосов
/ 10 октября 2008

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

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

typedef SomeClass::SomeContainer::iterator MyIter;

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

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

4 голосов
/ 11 октября 2008

Посмотрев на это в g ++ при оптимизации -O2 (точнее говоря)

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

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

4 голосов
/ 11 октября 2008

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

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

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

1 голос
/ 12 октября 2008

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

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

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

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

1 голос
/ 10 октября 2008

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

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

и есть ..

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

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

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

Вещи, которые не нравятся:

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

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

1 голос
/ 10 октября 2008

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

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

1 голос
/ 10 октября 2008

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

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

typedef SomeClass::SomeContainer::iterator sc_iter_t;

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

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

0 голосов
/ 10 октября 2008

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

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

for(...)
...