++это или это++ при переборе карты?

Примеры, показывающие, как перебирать std::map, часто бывают такими:

MapType::const_iterator end = data.end(); 
for (MapType::const_iterator it = data.begin(); it != end; ++it)

то есть он использует ++it вместо it++. Есть ли причина, почему? Могут ли возникнуть проблемы, если я вместо этого использую it++?


person laurent    schedule 03.08.2011    source источник
comment
Взгляните на [это] [1]. [1]: stackoverflow.com/questions/24853/   -  person sergio    schedule 03.08.2011
comment
Если вы программируете на C++ какое-то время, ++it значительно повысит эффективность. Не в вашем коде, а во времени, потраченном на объяснение людям, почему вы не сделали ++it в первую очередь. Поскольку это на самом деле не имеет ни малейшего значения, вы можете сделать это так, чтобы не возникало споров.   -  person Dennis Zickefoose    schedule 03.08.2011
comment
@Dennis: Это не имеет большого значения, много и очень часто не поддается измерению, но в узких циклах это может иметь значение.   -  person Christopher Creutzig    schedule 03.08.2011
comment
@Christopher: Люди так говорят, но никто никогда не показывает пример того, когда. Каждый тест, который я когда-либо запускал, приводил к тому, что результирующий код был настолько почти идентичен, что даже в жестких циклах о нем не стоило беспокоиться. Единственным исключением были случаи, когда операторы приращения были достаточно сложными, чтобы их нельзя было встроить [и, следовательно, посторонние временные параметры не удалялись].   -  person Dennis Zickefoose    schedule 03.08.2011
comment
@ Деннис: я должен был уточнить это больше, да. Я говорил о необычной ситуации со сложными итераторами.   -  person Christopher Creutzig    schedule 04.08.2011


Ответы (6)


Для проверки я сделал три исходных файла:

#include <map>

struct Foo { int a; double b; char c; };

typedef std::map<int, Foo> FMap;

### File 1 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(), end = m.end(); it != end; ++it)
    it->second = f;
}

### File 2 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); ++it)
    it->second = f;
}

### File 3 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); it++)
    it->second = f;
}

### end ###

После компиляции с помощью g++ -S -O3, GCC 4.6.1, я обнаружил, что версии 2 и 3 производят идентичную сборку, а версия 1 отличается только одной инструкцией, cmpl %eax, %esi против cmpl %esi, %eax.

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

person Kerrek SB    schedule 03.08.2011
comment
Хорошее отображение оптимизации, но это может работать только для достаточно простых и встроенных operator++. Приятно видеть, что g++ может сделать это для std::map. - person Christopher Creutzig; 03.08.2011
comment
@Christopher: Да, и вам определенно следует всегда использовать ++it, когда вы имеете в виду это. Мне просто захотелось провести жесткое сравнение истинного влияния двух часто рекламируемых пунктов (поднятие конца и использование приращения префикса). - person Kerrek SB; 03.08.2011

it++ возвращает копию предыдущего итератора. Поскольку этот итератор не используется, это расточительно. ++it возвращает ссылку на увеличенный итератор, избегая копии.

Подробное объяснение см. в вопросе 13.15.

person Dark Falcon    schedule 03.08.2011
comment
Вряд ли это будет иметь значение. Вопрос чисто в стиле. - person Cat Plus Plus; 03.08.2011
comment
@Dark - поскольку он не используется, компилятор, скорее всего, заметит это и оптимизирует копию. - person Bo Persson; 03.08.2011
comment
Я знаю об этом. Однако, очевидно, компилятор не обязан этого делать. - person Dark Falcon; 03.08.2011
comment
Я лично считаю, что предварительное увеличение читается лучше. Прочитайте «++» как приращение, и оно станет увеличиваться в вашей голове, а не увеличиваться - person Matt Greer; 04.08.2011
comment
Я бы не сказал, что это вопрос стиля. Предварительное приращение говорит: приращение. Пост-инкремент говорит: «Увеличь, но верни мне исходное значение, чтобы я мог что-то с ним сделать». Если вы не делаете что-то с исходным значением, то постинкремент не так ясно выражает ваше намерение. - person Ben; 04.08.2011
comment
Чисто вопрос стиля. Для меня Pre-increment говорит: «Упреждающе получить мне увеличенное значение переменной, потому что мне нужно что-то сделать с этим прямо сейчас в этой строке, потому что следующая строка будет слишком поздно». Помимо перегрузки оператора, постинкремент — это просто хороший читаемый синоним +=1, то есть присваивание, а переменная слева — правильное значение l. Программисты читают глаголы в простом настоящем времени: a=3; -> a становится 3. a++; -> a увеличивается. К сожалению, поскольку для многих это просто стиль, у нас всегда должен быть комментарий, объясняющий, почему это важно, если это так. - person Dewi Morgan; 30.01.2015

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

for (list<string>::const_iterator it = tokens.begin();
    it != tokens.end();
    ++it) { // Don't use it++
    ...
}

Причина становится понятной, когда вы думаете о том, как обычно реализуются оба оператора. Предварительное приращение довольно простое. Однако для того, чтобы пост-инкремент работал, вам нужно сначала сделать копию объекта, выполнить фактическое инкрементирование исходного объекта, а затем вернуть копию:

class MyInteger {
private:
    int m_nValue;

public:
    MyInteger(int i) {
        m_nValue = i;
    }

    // Pre-increment
    const MyInteger &operator++() {
        ++m_nValue;
        return *this;
    }

    // Post-increment
    MyInteger operator++(int) {
        MyInteger clone = *this; // Copy operation 1
        ++m_nValue;
        return clone; // Copy operation 2
    }
}

Как видите, реализация постинкремента включает две дополнительные операции копирования. Это может быть довольно дорого, если объект, о котором идет речь, громоздкий. Сказав это, некоторые компиляторы могут быть достаточно умны, чтобы обойтись одной операцией копирования посредством оптимизации. Дело в том, что пост-инкремент обычно требует больше работы, чем пре-инкремент, и поэтому разумно привыкнуть ставить ваши «++» перед вашими итераторами, а не после них.

(1) Кредит на связанный веб-сайт.

person Community    schedule 03.08.2011

С логической точки зрения - то же самое и здесь не имеет значения.

Почему используется префикс - потому что он быстрее - он меняет итератор и возвращает его значение, а постфикс создает временный объект, увеличивает текущий итератор и затем возвращает временный объект (копия того же итератора, до увеличения). Поскольку здесь никто не наблюдает за этим временным объектом (возвращаемое значение), это то же самое (логически).

Есть довольно большой шанс, что компилятор это оптимизирует.


Кроме того, на самом деле это должно быть так для любых типов вообще. Но это просто должно быть. Поскольку любой может перегрузить operator++ — постфикс и префикс, они могут иметь побочные эффекты и различное поведение.

Что ж, это ужасная вещь, но все же возможная.

person Kiril Kirov    schedule 03.08.2011
comment
Ужасная вещь на самом деле. Что-то вроде злоупотребления оператором сдвига для помещения данных в поток, верно? ;-) (ГКНР) - person Christopher Creutzig; 03.08.2011
comment
Что ж, это хороший момент — в конце концов, вы могли бы в конечном итоге иметь дело с пользовательским итератором, который очень дорого копировать по какой-либо причине и не может быть оптимизирован, поэтому просто по привычке писать то, что вы имеете в виду (++it), а не то, что вы можете привыкли к (c++) - это просто хорошая практика. - person Kerrek SB; 03.08.2011
comment
@Kerrek SB - точно :) Я всегда предпочитаю ++bla_bla, когда его можно использовать. - person Kiril Kirov; 03.08.2011

Никаких проблем это не вызовет, но правильнее использовать ++it. С маленькими типами не имеет значения использование ++i или i++, но для "больших" классов:

operator++(type x,int){
    type tmp=x; //need copy
    ++x;
    return tmp;
}

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

person RiaD    schedule 03.08.2011

Как уже говорилось в других ответах, предпочитайте ++ это, если оно не будет работать в контексте. Для перебора контейнеров небольших типов это действительно не имеет большого значения (или не имеет значения, если компилятор его оптимизирует), но для контейнеров больших типов это может иметь значение из-за экономии затрат на создание копии.

Правда, в вашем конкретном контексте вы можете знать, что тип достаточно мал, так что вам не о чем беспокоиться. Но позже кто-то из вашей команды может изменить содержимое контейнера там, где это будет иметь значение. Кроме того, я думаю, что лучше выработать у себя хорошую привычку и пост-инкрементировать только тогда, когда вы знаете, что должны.

person Matt Ryan    schedule 03.08.2011