Pointer to iterator II
Kontynuując temat konwersji wskaźnika do iteratora z poprzedniej notki, chciałbym rzucić nieco więcej światła i nowych spostrzeżeń jakie mnie naszły po popełnieniu wspomnianego wpisu, po którym nie mogłem spokojnie zasnąć ;)
Rewidując ponownie ten problem pod katem wydajnościowym i nie tylko doszedłem do wniosku, że w ogóle taka operacja jest bez sensu, uzycie takiej operacji w kodzie to bad design. Tak, świadczy to o złym projekcie i należałoby się dobrze zastanowić co dalej zrobić.
Gdy mamy opcje: wskaźnik czy iterator, powinniśmy bez namysłu wybrać iterator i z niego korzystać. W sytuacji, gdzie jakiś powodów mamy sam wskaźnik, a potrzebnym nam jest iterator to należy pomyśleć o zmianie projektu lub umożliwić wykonanie danej usługi bezpośrednio na wskazywanym obiekcie, o ile oczywiście jest to możliwe do zrealizowania.
Do potwierdzenia bezsensowności rozważmy przedstawione w poprzedniej notce sposoby wyznaczania iteratora na podstawie wskaźnika przy wykorzystaniu algorytmy find i find_if z biblioteki standardowej, przy założeniu, że implementacja wykorzystuje wskaźniki wewnątrz iteratorów, a nasz wskaźnik wskazuje na istniejący w kontenerze element.
Końcowa wersja funkcji z find po wygenerowaniu i rozwiniecie kodu przez kompilator, mogłaby wyglądać następująco:
Obj* find(Obj* first, Obj* last, const Obj& val) { while (first != last && *first != val) ++first; return first; } |
Jako argument val podajemy nasz wskaźnik, funkcja przechodzi przez wszystkie elementy do napotkania elementu na jaki wskazuje nasz wskaźnik, po czym zwraca ten sam wskaźnik.
W wyniku czego otrzymujemy wskaźnik, ten sam wskaźnik jaki mieliśmy, wiec nic nie otrzymaliśmy. Prócz wygenerowania zbędnego kodu i narzutu w czasie wykonania. Dodatkowo, prócz zbędnej pętli, której wykonanie rośnie liniowo ze wzrostem rozmiarów kontenera i dereferencjach w każdej iteracji, wykorzystywany jest operator porównania typu Obj, co przy „cięższym” typie może być trochę kosztowne.
Wersja z find_if będzie trochę lepsza:
Obj* find_if(Obj* first, Obj* last, const Obj* val) { while (first != last && first != val) ++first; return first; } |
Tutaj tylko porównanie wskaźników, ale nadal wykonanie pętli jest zbędne, bo z góry znamy wynik.
Oczywiście sprawa wygląda całkiem inaczej, gdy wskaźnik nie wskazuje na żaden element kontenera tylko na inny obiekt, a my chcemy znaleźć wystąpienie tego elementu w kontenerze i otrzymać odpowiedni iterator.
W innym wypadku (konwersja) jest to czysta głupota, świadczącą o źle zaprojektowanym projekcie. Powinniśmy wystrzegać się takich bezsensownych operacji.
Dobrze, że w moim projekcie, łatwo da się rozwiązać problem wskaźnik -> iterator.