Итератори

итератор

Итераторите са обобщени указатели, позволяващи на програмистите (алгоритмите) да работят със различни по вид контейнери по унифициран (общ) начин.

Видове итератори

Видове итератори

  • input iterator
  • output iterator
  • forward iterator
  • bidirectional iterator
  • random access iterator

В ред на нарастваща функционалност.

input iterator

  • най-ограничени възможности (заедно с output iterator)
  • istream_iterator,istreambuf_iterator- позволяват да разглеждаме файловете като контейнери
  • поддържат само one-pass алгоритми

input iterator

input_iterator x, y;
value = *x; // дереференциране
*x = value; // ERROR, read-only values
++x;
x++;
if (x == y) { } // сравняване
if (x != y) { } // сравняване
x = y; // копиране

istream_iterator, istreambuf_iterator

  • итераторите пазят последната прочетена стойност за да ползволяват многократно дереференциране
  • x++предизвиква прочитане на следващата стойност от файла
  • ако имате повече от единistream_iterator(istreambuf_iterator) и го инкрементирате ще получите изненадващи резултати (представете си, че четете едновременно един файл от две или повече функции)

output iterator

  • най-ограничени възможности (заедно с input iterator)
  • ostream_iterator,ostreambuf_iterator
  • отново поддържат само one-pass алгоритми

output iterator

input_iterator x, y;
*x = value; // дереференциране и записване на стойност
value = *x; // ERROR, write-only values
++x;
x++;
if (x == y) { } // сравняване
if (x != y) { } // сравняване
x = y; // копиране

ostream_iterator, ostreambuf_iterator

  • *x\=v;записва стойността във файла ипреместваитератора
  • ако имате повече от единostream_iterator(ostreambuf_iterator) асоцииран с един и същи файл и ги записвате ще получите изненадващи резултати (представете си, че пишете едновременно един файл от две или повече функции)

stream vs streambuf iterators

  • istream_iteratorиostream_iteratorизползват форматираните входно-изходни операции, т.е.operator<<
  • istreambuf_iteratorиostreambuf_iteratorизползват неформатирани входно-изходни операции и четат/пишат директно буфера на потока
  • Ако не се нуждаете от форматирани входно-изходни операции използвайтеistreambuf_iteratorиostreambuf_iterator- те са по - ефективни

forward iterator

  • позволяват няколкократно итериране (с няколко итератора)
  • само в една посока
  • нито един стандартен контейнер не предоставя forward iterators
  • slist::iteratorе forward iterator
  • категорията съществува за да представи изискванията на голяма част от алгоритмите към итераторите, върху които се прилагат

bidirectional iterator

  • позволяват няколкократно итериране (с няколко итератора)
  • и в двете посоки, но само последователно
  • list,set,map,multiset,multimapпредоставят bidirectional iterators

random access iterator

  • позволяват произволен достъп до обектите в контейнера
  • този достъп не се проверява за грешка
  • vectorиdequeпредоставят random access iterators

random access iterator

random_iterator x, y;
y = x + 5;
value v = x[42];

iterator tags

За всеки тип итератори има съответстващ C++ тип, който се нарича таг. (Всеки итератор, напримерvector::iterator, е таг-нат със своя семантичен тип итератор, в случая сrandom_access_iterator)

  • input_iterator_tag
  • output_iterator_tag
  • forward_iterator_tag
  • bidirectional_iterator_tag
  • random_access_iterator_tag

iterator traits

Позволяват достъп до характеристиките на даден итератор

  • типа на стойността, към която итераторът сочи
  • типа на указател към стойността, към която итераторът сочи
  • типа на псевдоним към стойността, към която итераторът сочи
  • типа на разликата на два итератори (x-y)

iterator traits

template <typename Iterator>
struct iterator_traits {
    typedef typename Iterator::difference_type difference_type;
    typedef typename Iterator::value_type value_type;
    typedef typename Iterator::pointer pointer;
    typedef typename Iterator::reference reference;
    typedef typename Iterator::iterator_category iterator_category;
} ;

?

Защо имаiterator_traitsкогато явно итераторите сами иматtypedefсъс всичко необходимо?

Отговор

T*също е итератор, но няма никакъвtypedefв себе си

iterator_traits

template <typename T>
struct iterator_traits<T*> {
    typedef ptrdiff_t difference_type;
    typedef T value_type;
    typedef T* pointer;
    typedef T& reference;
    typedef random_access_iterator_tag iterator_category;
} ;

Защо ни еiterator_traits?

// note: stupid example
template <typename Iterator>
void swap_values(Iterator x, Iterator y)
{
    typename iterator_traits<Iterator>::value_type tmp = *x;
    *x = *y;
    *y = tmp;
}

advance()

template <typename Iterator, typename Distance>
void advance(Iterator& i, Distance n)
{
    // ???
    while (n--) ++i;
}
// доста бавно за random_access iterator

advance()

template <typename Iterator, typename Distance>
void advance(Iterator& i, Distance n)
{
    advance_impl(i, n, iterator_traits<Iterator>::iterator_category());
}

advance_impl()

template <typename Iterator, typename Distance>
void advance_impl(Iterator& i, Distance n, random_iterator_tag tag)
{
    i += n;
}

template <typename Iterator, typename Distance, typename Tag>
void advance_impl(Iterator& i, Distance n, Tag tag)
{
    while (n--) ++i;
}

iterator adaptors

  • front_inserter
  • back_inserter
  • insert_iterator

Дефинирание са в

front_inserter

  • писането в този итератор извикваpush_frontна контейнера
  • Истинския тип еstd::front_insert_iterator, но много рядко се налага да се ползва явно.
  • std::front_inserterе функция, която връща такъв итератор

Пример за front_inserter

vector<int> v;
list<int> l;
// ...
copy(v.begin(), v.end(), front_inserter(l));
// това ще обърне реда на елементите на v в r

back_inserter

  • писането в този итератор извикваpush_backна контейнера
  • типът на итератора еstd::back_insert_iterator
  • std::back_inserterе функция, която връща такъв итератор

insert_iterator

  • писането в този итератор правиinsertвъв контейнера. Първияinsertсе прави в позицията определена при създаването на итератора, а всеки следващ - в позицията след предходно вмъкнатия елемент
  • std::inserter(container,it)създава такъв итератор

Пример за insert_iterator

list<int> l, r;
list<int>::iterator i;
for (int i = 0; i < 5; ++i)
    l.push_back(i);
r = l;
i = r.begin();
advance(i, 3);
copy(l.begin(), l.end(), inserter(r, i))
// 0 1 2 0 1 2 3 4 3 4

const_iterator

  • всички контейнери предоставятconst_iterator

  • повечето методи на контейнерите изискватiterator

  • за да превърнетеconst_iteratorвiteratorизползвайте:

    Vector v;
    Vector::const_iterator c;
    // ...
    Vector::iterator i = advance(v.begin(), distance(v.begin, c));
    // работи за всички видове, контейнери
    // ще бъде О(N) за forward iterators
    

reverse_iterator

  • позволяват обхождането на елементите в контейнерите да става в обратен ред
  • за да се запазят изискванията за валидност на указателитеreverse_iteratorсочи към елемента след (при нормално обхождане) текущия
  • reverse_iteratorима метод base(), който връща нормален итератор към сочещ към елемента след (при нормално обхождане) след текущия
vector<int> v;
// ... // v is [0, 1, 2, 3]
vector<int>::reverse_iterator i = v.rbegin();
// i.base() == v.end()
++i;
// *i == 2;
// *(i.base()) == 3;

Вмъкване и изтриване през reverse_iterator

  • c.insert(i,v)- вмъкваvпредиi
  • c.insert(ri.base(),v)- вмъкваvпредиreverse_iterator-ariпогледнато отзад напред
  • c.erase((++ri).base())ще изтрие елемента сочен отri

Custom iterator

Writing custom iterator is a tedious task

Bare minimum

  • all typedef
  • operator *, operator ->
  • operator ==, operator !=
  • operator ++, operator ++(int)

Random-access iterator

All pointer-like semantics:

  • operator[]
  • operator <, operator <=,
  • operator >, operator >=,
  • explicit operator bool (or SafeBool idiom)
  • operator !

Extra

  • helper functions for creating instances

Future for iterators

Iterators must go

Andrei Alexandrescu

BoostCon 2009

Slides

  • Iterators are error-prone
    • You always need begin and end of the same container
    • Given two iterators there is no way to detect whether they belong to the same container
  • Writing a custom iterator is too much code
  • Iterators are almost non-composable

range

  • basically it is the pair [begin, end)
  • doesn't have to be pointer-like
  • easily composable