sobota, 7 kwietnia 2012

Standard C++11: pętla for oparta na zakresie

Od kilku miesięcy znamy nowy standard języka C++, noszący nieoficjalną nazwę C++11. Najpopularniejsze kompilatory stopniowo dostosowują się do nowego standardu. Warto wiedzieć co nowego zostało dodane do języka, gdyż niektóre rzeczy na pewno ułatwią i przyspieszą pisanie kodu. Będę starał się w miarę regularnie przybliżać funkcjonalności, które działają już w najnowszych wersjach wielu kompilatorów. Na początek: pętla for oparta na zakresie.


1. Składnia i zastosowanie

Działanie nowej pętli najłatwiej będzie wytłumaczyć na przykładzie:

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

for(int i : vec) {
  cout << i;
}

Powyższy kod wypisuje na standardowe wyjście każdy element wektora vec. Ważne jest, aby możliwa była konwersja elementów wektora to typu zmiennej i. Nie jest możliwe zatem użycie następującego kodu:

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

for(string i : vec) {
  cout << i;  // błąd kompilacji!
}

We wcześniejszym przykładzie zmienna i za każdym razem przechowywała kopię danego elementu wektora vec. Edycja zmiennej i wewnątrz pętli, nie zmieniłaby zatem elementów wektora. Nic nie stoi jednak na przeszkodzie, by zmienna i była na przykład referencją do elementów wektora vec:

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

for(int& i : vec) {
  ++i;
}

Po wykonaniu powyższego kodu wektor vec przechowywać będzie wartości 2, 3 i 4.

Pętla for oparta na zakresie działa dla wszystkich kontenerów wchodzących w skład biblioteki STL, typów string oraz nawet zwykłych tablic:

int array[] = {11, 22, 33, 44, 55};
for(int& x : array) {
  x *= 2;
}

2. Tworzenie własnych typów kompatybilnych z nową pętlą

Nowa pętla może być użyta z każdym typem, po którym jest możliwa iteracja, a więc musi on "definiować jakiś zakres". Jeżeli chcielibyśmy zastosować pętlę dla stworzonego przez nas typu, musi on być zgodny z konwencją przyjętą w bibliotece STL i zdefiniowanymi tam iteratorami:

  1. Muszą istnieć metody begin oraz end, zdefiniowane jako składowe klasy bądź wolne funkcje, zwracające iteratory odpowiednio na początek i koniec sekwencji.
  2. Iteratory muszą działać z operatorami *, != oraz prefiksowym ++.

Poniżej znajduje się przykładowy program demonstrujący użycie nowej pętli for z typem zdefiniowanym przez użytkownika.

#include <algorithm>
#include <cstddef>
#include <cstdio>

template<typename T, std::size_t N>
class MyArray;

template<typename T, std::size_t N>
class MyArrayIter {
public:
  MyArrayIter(const MyArray<T, N>& myArray_, std::size_t pos_)
    : myArray(myArray_), pos(pos_) {}

  bool operator!=(const MyArrayIter<T, N>& iter) {
    return pos != iter.pos;
  }

  T operator*() const;

  const MyArrayIter<T, N>& operator++() {
    ++pos;
    return *this;
  }

private:
  const MyArray<T, N>& myArray;
  std::size_t pos;
};

template<typename T, std::size_t N = 3>
class MyArray {
public:
  typedef MyArrayIter<T, N> iterator;

  MyArray() {
    std::fill(data, data + N, T()); // wypełnianie wartościami domyślnymi
  }

  iterator begin() const {
    return MyArrayIter<T, N>(*this, 0);
  }

  iterator end() const {
    return MyArrayIter<T, N>(*this, N);
  }

  T operator[](std::size_t i) const {
    return data[i];
  }

private:
  T data[N];
};

template<typename T, std::size_t N>
T MyArrayIter<T, N>::operator*() const {
  return myArray[pos];
}

int main() {
  MyArray<int> myArray;
  for(int x : myArray) { // iteracja po typie użytkownika
    printf("%d\n", x);
  }
}

Powyższy program wyświetla trzy zera. ;)


3. Wsparcie kompilatorów

Pętla for oparta na zakresie działa w kompilatorach Clang od wersji 3.0 oraz GCC od wersji 4.6. Microsoft Visual C++ będzie posiadał nową pętlę w wersji 2011 (aktualnie dostępna w VC11 Beta).



Pliki do pobrania:

Brak komentarzy:

Prześlij komentarz