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

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
0
4 773
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

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

typedef SomeClass::SomeContainer::iterator MyIter;

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

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

не обязательно, в моей среде это является законным и действительным: "for (int i = 0; i <max; ++ i) {} newax = i;"

steffenj 10.10.2008 00:27

@Ferrucio: это недопустимый C++ - возможно, вы используете старую версию VC++, которая не соответствовала стандарту, но вам придется исправить это при обновлении

1800 INFORMATION 10.10.2008 00:29

Правда, старые компиляторы могут по-прежнему принимать старый стандарт. С VC++ у вас действительно есть возможность включить / выключить это.

Ferruccio 10.10.2008 00:30

Вызов end () на каждой итерации может повлиять на производительность в зависимости от того, как реализован контейнер.

Ates Goral 10.10.2008 00:30

@steffenj, этот код, который «работает» в вашей среде, не является стандартным C++ (Visual C++ плохо относился к этому до 2008 года)

crashmstr 10.10.2008 00:32

Это действительно как по старым, так и по новым стандартам. Просто по новому стандарту область действия ограничена циклом for. Согласно старому стандарту итератор был бы виден до конца области, в которой находится цикл for.

Ferruccio 10.10.2008 00:33

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

Greg Rogers 10.10.2008 00:44

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

KeithB 10.10.2008 15:58

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

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

вы имеете в виду второй вариант, а не первый?

steffenj 10.10.2008 00:29

Я согласен с Ферруччо. Некоторые могут предпочесть первый стиль, чтобы вывести вызов 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) {}

Ferruccio 10.10.2008 00:35

Тогда весь этот разговор будет спорным. Ура!

Fred Larson 10.10.2008 01:54

Обычно я писал:

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 = ...

paercebal 10.10.2008 01:13

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

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

typedef SomeClass::SomeContainer::iterator sc_iter_t;

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

Не большое улучшение, но небольшое.

Хорошая идея. Я сам использовал это пару раз, но недостаточно недавно, чтобы не забыть прокомментировать это. Это делает код много более читабельным.

Herms 10.10.2008 00:38

У меня нет опыта работы с консолями, но в большинстве современных компиляторов 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
   }
 }

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

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

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

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

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

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

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

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

Если ваш цикл изменяет контейнер, то увеличивающийся итератор также становится недействительным, поэтому вы, если вы используете этот тип цикла «foreach», можете также предварительно инициализировать end.

Chris Blackwell 20.04.2009 09:10

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

wilhelmtell 20.04.2009 15:13

Посмотрев на это в 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)
{
   ...
}

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

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