++ это или это ++ при переборе карты? - PullRequest
7 голосов
/ 03 августа 2011

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

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

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

Ответы [ 6 ]

20 голосов
/ 03 августа 2011

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

Более полное объяснение см. Вопрос 13.15 .

11 голосов
/ 03 августа 2011

Испытывая его, я сделал три исходных файла:

#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, вероятно, лучше, поскольку оно наиболее точно отражает ваши требования, но не зацикливайтесь на этом.

7 голосов
/ 03 августа 2011

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

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) Кредит на связанный сайт.

6 голосов
/ 03 августа 2011

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

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

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


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

Ну, это ужасно, но все же возможно.

1 голос
/ 04 августа 2011

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

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

1 голос
/ 03 августа 2011

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

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

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

...