Считается ли инициализация итератора внутри цикла for плохим стилем, и почему?

StackOverflow https://stackoverflow.com/questions/189055

Вопрос

Обычно вы найдете 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?

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

typedef SomeClass::SomeContainer::iterator MyIter;

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

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

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

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

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

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

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

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

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

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

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

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

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 () на каждой итерации, я получаю это

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

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

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

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

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

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

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

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

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

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

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

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

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

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

for(...)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top