﻿# Есть ли практический смысл использовать для итераторов префиксный оператор инкремента \+\+it, вместо постфиксного it\+\+

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

Но вот насколько эта рекомендация актуальна в наше время? В стародавние времена, например, советовали не повторять вычисления\. Считалось хорошим тоном вместо:

```cpp
X = A + 10 + B;
Y = A + 10 + C;
```

написать так:

```cpp
TMP = A + 10;
X = TMP + B;
Y = TMP + C;
```

Сейчас такая мелочная ручная оптимизация бессмысленна\. Компилятор справится с такой задачей не хуже\. Только лишнее загромождение кода\.

_Примечание для особенно педантичных\. Да, лучше не повторять вычисления и длинные выражения, которые используются несколько раз, вычислить отдельно\. Я говорю, что нет смысла оптимизировать простые случаи наподобие того, что я привёл\._

Мы отвлеклись\. На повестке дня вопрос, а не устарел ли совет использовать префиксный инкремент для итераторов вместо постфиксного\. Стоит ли забивать голову очередной тонкостью\. Возможно, компилятор уже давно умеет оптимизировать постфиксный инкремент\.

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

Префиксный оператор инкремента изменяет состояние объекта и возвращает себя в уже изменённом виде\. Оператор префиксного инкремента в классе итератора для работы с _std::vector_ может выглядеть так:

```cpp
_Myt& operator++()
{ // preincrement
  ++_Myptr;
  return (*this);
}
```

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

```cpp
_Myt operator++(int)
{ // postincrement
  _Myt _Tmp = *this;
  ++*this;
  return (_Tmp);
}
```

Если мы хотим только увеличить значение итератора, то получается, что префиксная форма предпочтительна\. Поэтому, один из советов по микро\-оптимизации программ писать _for \(it \= a\.begin\(\); it \!\= a\.end; \+\+it\)_ вместо _for \(it \= a\.begin\(\); it \!\= a\.end; it\+\+\)_\. В последнем случае происходит создание ненужного временного объекта, что снижает производительность\.

Более подробно все это можно почитать в книге Скотта Мейерса "Наиболее эффективное использование С\+\+\. 35 новых рекомендаций по улучшению ваших программ и проектов" \(Правило 6\. Различайте префиксную форму операторов инкремента и декремента\) \[1\]\.

Теория закончилась\. Перейдем к практике\. Есть ли смысл в коде заменить постфиксный инкремент на префиксный?

```cpp
size_t Foo(const std::vector<size_t> &arr)
{
  size_t sum = 0;
  std::vector<size_t>::const_iterator it;
  for (it = arr.begin(); it != arr.end(); it++)
    sum += *it;
  return sum;
}
```

Я понимаю, что можно уйти в область философии\. Мол, потом, возможно, контейнером будет не _std::vector_, а другой класс, где итераторы очень сложные и тяжёлые объекты\. При копировании итератора надо делать новое подключение к базе данных, ну или что\-то в этом духе\. Следовательно, всегда следует писать _\+\+it_\.

Но это в теории\. А вот встретив на практике где\-то в коде такой цикл, есть смысл заменить _it\+\+_ на _\+\+it_? Не лучше ли положиться на компилятор, который и без нас догадается, что лишний итератор можно выбросить?

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

Да, нужно заменить _it\+\+_ на _\+\+it_\.

Да, компилятор произведёт оптимизацию и не будет никакой разницы, какой инкремент использовать\.

Я выбрал "средний компилятор" и создал тестовый проект для Visual Studio 2008\. В нём есть две функции, считающие сумму с использованием _it\+\+_ и _\+\+it_, а также измерение времени их работы\. Скачать проект можно [здесь](https://pvs-studio.com/external-pictures/testspeediterator.zip)\. Код функций, скорость которых замерялась:

1\) Постфиксный инкремент\. _iterator\+\+_\.

```cpp
std::vector<size_t>::const_iterator it;
for (it = arr.begin(); it != arr.end(); it++)
  sum += *it;
```

2\) Префиксный инкремент\. _\+\+iterator_\.

```cpp
std::vector<size_t>::const_iterator it;
for (it = arr.begin(); it != arr.end(); ++it)
  sum += *it;
```

Скорость работы в Release версии:

```cpp
iterator++. Total time : 0.87779
++iterator. Total time : 0.87753
```

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

А теперь ответим на вопрос, зачем же тогда стоит менять it\+\+ на \+\+it\. Замерим скорость работы Debug версии:

```cpp
iterator++. Total time : 83.2849
++iterator. Total time : 27.1557
```

Писать код так, чтобы замедляться в 30, а не в 90 раз, вполне имеет практический смысл\.

Конечно, для многих скорость работы отладочных \(Debug\) версий не так важна\. Однако если программа делает что\-то существенное по времени, то такое замедление может быть критично\. Например, с точки зрения юнит\-тестов\. А значит оптимизировать скорость работы отладочной версии имеет смысл\.

Дополнительно я провел эксперимент, что будет, если использовать для индексации старый добрый [_size\_t_](https://pvs-studio.ru/ru/blog/terms/0044/)\. Это, конечно, к рассматриваемой теме не относится\. Я понимаю, что итераторы нельзя сравнивать с индексами, и что это более высокоуровневые сущности\. Но всё равно из любопытства написал и замерил скорость следующих функций:

1\) Классический индекс типа _size\_t_\. _i\+\+_\.

```cpp
for (size_t i = 0; i != arr.size(); i++)
  sum += arr[i];
```

2\) Классический индекс типа _size\_t_\. _\+\+i_\.

```cpp
for (size_t i = 0; i != arr.size(); ++i)
  sum += arr[i];
```

Скорость работы в Release версии:

```cpp
iterator++. Total time : 0.18923
++iterator. Total time : 0.18913
```

Скорость работы в Debug версии:

```cpp
iterator++. Total time : 2.1519
++iterator. Total time : 2.1493
```

Как и следовало ожидать, скорость работы _i\+\+_ и _\+\+i_ совпала\.

Примечание\. Код с _size\_t_ работает быстрее по сравнению с итераторами за счет того, что отсутствует проверка на выход за границу массива\. Цикл с итераторами можно сделать столь же быстрым в Release\-версии, вписав _\#define \_SECURE\_SCL 0_\.

Чтобы было проще оценить результаты замеров скорости, представлю их виде таблицы \(рисунок 1\)\. Результаты я пересчитал, взяв за единицу время работы Release версии с _iterator\+\+_\. И еще немного округлил числа для простоты\.

![0093_Iterators_ru/image1.png](https://import.viva64.com/docx/blog/0093_Iterators_ru/image1.png)

Рисунок 1\. Время работы алгоритмов вычисления суммы\.

Каждый сам может сделать для себя выводы\. Они зависят от типа решаемых задач\. Для себя лично я сделал следующие:

1. Я убедился в целесообразности такой микрооптимизации\. В [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) стоит реализовать поиск постфиксного увеличения итератора, если его предыдущее состояние не используется\. Некоторые программисты сочтут эту функциональность полезной\. А остальные, если она мешает, всегда смогут в настройках отключить данную диагностику\. 
1. Я всегда буду писать _\+\+it_\. Я и раньше так делал\. Но делал это "на всякий случай"\. Теперь я вижу пользу от этого, так как мне важен регулярный запуск отладочных версий\. Конечно, в целом на время работы _\+\+it_ окажет очень маленький эффект\. Но если в разных местах не делать вот такие небольшие оптимизации, то потом будет поздно\. Профайлер мало чем поможет\. Медленные места будут "размазаны тонким слоем" по всему коду\.
1. Я замечаю, что все больше времени анализатор PVS\-Studio проводит внутри различных функций классов _std::vector_, _std::set_, _std::string_ и так далее\. Это время растёт и растёт, так как появляются все новые и новые диагностические правила, а их удобно писать как раз с использованием STL\. Вот думаю, не пришло ли то самое страшное время, когда в программе появляются свои собственные специализированные классы строк, массивов и так далее\. Но это я о своём\.\.\. Вы меня не слушайте\! Я крамольные вещи говорю\.\.\. Тссс\.\.\.

P\.S\.:

Сейчас кто\-то скажет, что преждевременная оптимизация \- это зло \[2\]\. Когда нужна оптимизация, надо брать профайлер и искать узкие места\. Это я всё знаю\. И конкретных узких мест у меня давно нет\. Но вот когда ждёшь завершения тестов в течение 4 часов, начинаешь подумывать, что выиграть даже 20% скорости \- это уже хорошо\. А складывается вот такая оптимизация из итераторов, размеров структур, местами отказом от STL или Boost и так далее\. Думаю, некоторые разработчики меня хорошо поймут\.

## Библиографический список

1. Мейерс С\. Наиболее эффективное использование С\+\+\. 35 новых рекомендаций по улучшению ваших программ и проектов: Пер\. с англ\. \- М\.: ДМК Пресс, 2000\. \- 304 с\.: ил\. \(Серия "Для программистов"\)\. ISBN 5\-94074\-033\-2\. ББК 32\.973\.26\-018\.1\.
1. Елена Сагалаева\. Преждевременная оптимизация\. [http://alenacpp\.blogspot\.ru/2006/08/blog\-post\.html](http://alenacpp.blogspot.com/2006/08/blog-post.html) 
1. По мотивам этой статьи\. Pre vs\. post increment operator – benchmark\. [http://silviuardelean\.ro/2011/04/20/pre\-vs\-post\-increment\-operator/](https://silviuardelean.ro/2011/04/20/pre-vs-post-increment-operator/)