Zasadniczo iterowanie po elementach
tablicy/vectora/listy itp. nie jest niczym nowym, interesującym ani
pasjonującym, ot szara codzienność. Przyjrzyjmy się zatem jak
robimy to najczęściej:
class Image
{
public:
Image();
void rotate(float angle);
void display();
void* serialize();
};
class ShowImage
{
public:
void operator()(Image image)
{
image.display();
}
};
//...
std::vector<Image>
imageCollection(10);
for(int i=0; i<imageCollection.size();
++i)
{
imageCollection[i].display();
}
for( std::vector<Image>::iterator
currentImage = imageCollection.begin();
currentImage <
imageCollection.end();
++currentImage )
{
currentImage->display();
}
std::for_each(imageCollection.begin(),
imageCollection.end(),
ShowImage());
Dla naszych potrzeb stworzyliśmy
specjalną klasę Image (jej implementacja oraz funkcjonalność nie
jest istotna dla naszych potrzeb). Tworzymy 10 elementowy wektor
obiektów klasy Image, a następnie iterujemy po nich w 3
najpopularniejsze sposoby. Traktując wektor jak zwykłą tablicę,
wykorzystując iteratory, oraz za pomocą algorytmu for_each i
funktora. Na chwile obecną jedną rzeczą na którą warto zwrócić
uwagę to sposób inkrementacji indeksów(++i jest szybsze niż i++
gdyż nie wymaga kopiowania).
Powyższy kod nie jest niczym ciekawym,
na szczęście z pomocą przychodzi nam nowy standard języka C++
czyli wyrażenia lambda oraz specjalna składnia dla pętli for. Bez
zbędnych opóźnień przyjrzyjmy się implementacji.
for(auto it:imageCollection)
{
it.display();
}
std::for_each(imageCollection.begin(),
imageCollection.end(),
[](Image
image){image.display();});
Zastanówmy się jak dział nowa wersja
pętli for. Niepostrzeżenie wprowadziłem również nowe słówko
kluczowe auto pozwala ono na automatyczną dedukcję typów (w tym
przypadku typem wydedukowanym będzie Image). Znacznie bardziej
interesujące jest to co znajduje się po dwukropku, jest to nasz
wektor obiektów Image. Jak to działa? Otóż pętla for w tej
wersji pobiera ze wskazanego kontenera iteratory begin i end a
następnie dla wszystkich elementów pomiędzy nimi wykonuje kod ujęty w klamrach. Tak naprawdę jako kontenera możemy
użyć czegokolwiek co dostarcza metody begin, end i operatora
mniejszości dla iteratorów zwracanych przez begin i end. Całkiem
proste i przyjemne.
A co z drugim przykładem? Mamy tu
dobrze znany algorytm for_each przyjmujący jak zwykle iterator do
elementu pierwszego i adres obszaru pamięci za ostatnim elementem w
kontenerze. W miejscu funktora/wskaźnika do funkcji znajduje się
wyrażenie lambda. Lambda jest specjalnym przykładem funkcji
implementowanej w miejscu (ciekawostka: w objective-c mamy do
czynienia z blokami). Składnie lambdy w C++11 można pokrótce
przedstawić następująco [](){}. W nawiasach kwadratowych []
podajemy sposób dostępu do danych z zakresu w jakim znajduje się
nasze wyrażenie ([&] - oznacza dostęp do wszystkich zmiennych
przez referencję, [=] oznacza dostęp do wszystkich zmiennych przez
wartość, kwantyfikatory dostępu można składać i łączyć np.
[&x, =] ). Nawias okrągły () zawiera parametry naszej metody. W
nawiasach klamrowych {} znajduje się ciało funkcji.
Kiedy używać danej metody? Na to
pytanie nigdy nie ma prostej odpowiedzi. Z ust wielu kolegów
programujących w C++ często słyszałem że traktowanie wektora jak
tablice wygląda nieprofesjonalnie.
PS
Zachęcam do wykonania testów
wydajnościowych dla podanych metod i podzielenia się wynikami w
komentarzach
Komentarze
Prześlij komentarz