Будет ли оптимизация операторов postfix ++/- оптимизирована для итераторов STL?

Я знаю, что постфиксные версии операторов приращения/декремента будут, как правило, оптимизированы компилятором для встроенных типов (т.е. копия не будет сделана), но так ли это для iterator s?

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

#include <vector> 

void foo(std::vector<int>& v){
  for (std::vector<int>::iterator i = v.begin();
       i!=v.end();
       i++){  //will this get optimised by the compiler?
    *i += 20;
  }
}

Ответ 1

В конкретном случае std::vector в реализации GNU GCC STL (версия 4.6.1) я не думаю, что на достаточно высоких уровнях оптимизации будет разница в производительности.

Реализация для итераторов вперед на vector предоставляется __gnu_cxx::__normal_iterator<typename _Iterator, typename _Container>. Давайте посмотрим на его конструктор и постфикс ++:

  explicit
  __normal_iterator(const _Iterator& __i) : _M_current(__i) { }

  __normal_iterator
  operator++(int)
  { return __normal_iterator(_M_current++); }

И его экземпляр в vector:

  typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;

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

Но действительно ли он оптимизирован? Пусть узнают. Тестовый код:

#include <vector>

void test_prefix(std::vector<int>::iterator &it)
{
    ++it;
}

void test_postfix(std::vector<int>::iterator &it)
{
    it++;
}

Выходная сборка (на -Os):

    .file   "test.cpp"
    .text
    .globl  _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .type   _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, @function
_Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE:
.LFB442:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    addl    $4, (%eax)
    popl    %ebp
    .cfi_def_cfa 4, 4
    .cfi_restore 5
    ret
    .cfi_endproc
.LFE442:
    .size   _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, .-_Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .globl  _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .type   _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, @function
_Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE:
.LFB443:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    addl    $4, (%eax)
    popl    %ebp
    .cfi_def_cfa 4, 4
    .cfi_restore 5
    ret
    .cfi_endproc
.LFE443:
    .size   _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, .-_Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .ident  "GCC: (Debian 4.6.0-10) 4.6.1 20110526 (prerelease)"
    .section    .note.GNU-stack,"",@progbits

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

Конечно, это может быть не обязательно для пользовательских итераторов или более сложных типов данных. Но похоже, что для vector в частности префикс и постфикс (без учета возвращаемого значения postfix) имеют одинаковую производительность.