Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00066 008941 11061361 na godz. na dobę w sumie
STL w praktyce. 50 sposobów efektywnego wykorzystania - książka
STL w praktyce. 50 sposobów efektywnego wykorzystania - książka
Autor: Liczba stron: 296
Wydawca: Helion Język publikacji: polski
ISBN: 83-7361-373-0 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c++ - programowanie
Porównaj ceny (książka, ebook, audiobook).

Standard Template Library to jedno z najpotężniejszych narzędzi programistycznych charakteryzujące się złożonością i wysokim stopniem komplikacji. W książce 'STL w praktyce. 50 sposobów efektywnego wykorzystania' -- przeznaczonej dla zaawansowanych i średnio zaawansowanych programistów C++ -- znany ekspert Scott Meyers prezentuje najważniejsze techniki stosowania tego narzędzia. Poza listą zaleceń, jak postępować należy, a jak postępować się nie powinno, Meyers przedstawia wiele poważniejszych aspektów STL pozwalających zrozumieć, dlaczego pewne rozwiązania są poprawne, a innych należy unikać. Do każdej wskazówki dołączony jest kod źródłowy i dokładne objaśnienia, które powinny zainteresować zaawansowanych programistów.

Jeżeli Twoja wiedza ogranicza się do informacji dostępnych w dokumentacji STL, ta książka uzupełni ją o bezcenne wskazówki, które pozwolą Ci wykorzystać STL w praktyce.

Książka przedstawia:

Książkę uzupełniają dodatki, w których znajdziesz między innymi obszerną bibliografię na temat STL oraz cenne wskazówki dla programistów używających kompilatorów firmy Microsoft.

'STL w praktyce. 50 sposobów efektywnego wykorzystania' to nieocenione źródło wiedzy na temat jednego z najważniejszych aspektów programowania w C++. Jeżeli chcesz w praktyce wykorzystać STL, nie obędziesz się bez informacji zawartych w tej książce.

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

IDZ DO IDZ DO PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ SPIS TREĎCI SPIS TREĎCI KATALOG KSI¥¯EK KATALOG KSI¥¯EK KATALOG ONLINE KATALOG ONLINE ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG TWÓJ KOSZYK TWÓJ KOSZYK DODAJ DO KOSZYKA DODAJ DO KOSZYKA CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE ZAMÓW INFORMACJE O NOWOĎCIACH O NOWOĎCIACH ZAMÓW CENNIK ZAMÓW CENNIK CZYTELNIA CZYTELNIA STL w praktyce. 50 sposobów efektywnego wykorzystania Autor: Scott Meyers T³umaczenie: Adam Majczak (rozdz. 1-5), Wojciech Moch (rozdz. 6, 7, dod. A-C) ISBN: 83-7361-373-0 Tytu³ orygina³u: Effective STL 50 Specific Ways to Improve Your Use of the Standard Template Library Format: B5, stron: 282 Standard Template Library to jedno z najpotê¿niejszych narzêdzi programistycznych charakteryzuj¹ce siê z³o¿onoġci¹ i wysokim stopniem komplikacji. W ksi¹¿ce „STL w praktyce. 50 sposobów efektywnego wykorzystania” — przeznaczonej dla zaawansowanych i ġrednio zaawansowanych programistów C++ — znany ekspert Scott Meyers prezentuje najwa¿niejsze techniki stosowania tego narzêdzia. Poza list¹ zaleceñ, jak postêpowaæ nale¿y, a jak postêpowaæ siê nie powinno, Meyers przedstawia wiele powa¿niejszych aspektów STL pozwalaj¹cych zrozumieæ, dlaczego pewne rozwi¹zania s¹ poprawne, a innych nale¿y unikaæ. Do ka¿dej wskazówki do³¹czony jest kod ĥród³owy i dok³adne objaġnienia, które powinny zainteresowaæ zaawansowanych programistów. Je¿eli Twoja wiedza ogranicza siê do informacji dostêpnych w dokumentacji STL, ta ksi¹¿ka uzupe³ni j¹ o bezcenne wskazówki, które pozwol¹ Ci wykorzystaæ STL w praktyce. FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE Ksi¹¿ka przedstawia: • Podstawowe informacje o bibliotece STL i jej zgodnoġci z innymi standardami • Wskazówki dotycz¹ce poprawnego doboru i u¿ywania pojemników • Opis pojemników typu vector i string • Omówienie pojemników asocjatywnych • Metody w³aġciwego korzystania z iteratorów • Algorytmy wchodz¹ce w sk³ad STL • Funktory, klasy-funktory i funkcje • Programowanie za pomoc¹ biblioteki STL Ksi¹¿kê uzupe³niaj¹ dodatki, w których znajdziesz miêdzy innymi obszern¹ bibliografiê na temat STL oraz cenne wskazówki dla programistów u¿ywaj¹cych kompilatorów firmy Microsoft. „STL w praktyce. 50 sposobów efektywnego wykorzystania” to nieocenione ĥród³o wiedzy na temat jednego z najwa¿niejszych aspektów programowania w C++. Je¿eli chcesz w praktyce wykorzystaæ STL, nie obêdziesz siê bez informacji zawartych w tej ksi¹¿ce. Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: helion@helion.pl Spis treści Podziękowania...................................................n................................ 9 Przedmowa...................................................n................................... 13 Wprowadzenie ...................................................n.............................. 17 Rozdział 1. Kontenery...................................................n..................................... 29 Zagadnienie 1. Uważnie dobierajmy kontenery.................................................................30 Zagadnienie 2. Nie dajmy się zwieść iluzji o istnie.niu kodów niezależnych do zastosowanego kontenera.........................................................................................35 Zagadnienie 3. Kopiowanie obiektów w kontenerach powinn.o być „tanie”, łatwe i poprawne ............................................................................................................40 Zagadnienie 4. Stosujmy metodę empty(), zamiast przyrówny.wać rozmiar size() do zera....................................................................................................43 Zagadnienie 5. Preferujmy metody operujące na całych zakresa.ch (podzbiorach), bo są bardziej efektywne niż ich odpowiedniki operujące pojedynczymi elementami ...45 Zagadnienie 6. Bądźmy wyczuleni i przygotowani na najbar.dziej kłopotliwą interpretację kompilatora C++ ....................................................................56 Zagadnienie 7. Gdy stosujemy kontenery zawierające wsk.aźniki zainicjowane za pomocą operatora new, pamiętajmy o zwolnieniu dynamic.znej pamięci operatorem delete, zanim zawierający je obiekt-kontener sam zostanie usunięty.......59 Zagadnienie 8. Nigdy nie twórzmy kontenerów zawierając.ych wskaźniki kategorii auto_ptr ...........................................................................................................64 Zagadnienie 9. Uważnie dobierajmy opcje do operacji kasowania ..................................67 Zagadnienie 10. Uważajmy na konwencje dynamicznej al.okacji pamięci i stosowne ograniczenia ................................................................................................73 Zagadnienie 11. Zrozumienie uprawnionych zastosowań alo.katorów pamięci tworzonych przez użytkownika.....................................................................................80 Zagadnienie 12. Miejmy umiarkowane i realistyczne ocze.kiwania odnośnie poziomu bezpieczeństwa wielu wątków obsługiwanych przez kontenery STL ..........84 6 Spis treści Rozdział 2. Kontenery typu vector oraz string...................................................n.. 89 Zagadnienie 13. Lepiej stosować kontenery vector oraz .string niż dynamicznie przydzielać pamięć tablicom.........................................................................................90 Zagadnienie 14. Stosujmy metodę reserve, by uniknąć zbęd.nego przemieszczania elementów w pamięci....................................................................................................93 Zagadnienie 15. Bądźmy ostrożni i wyczuleni na zróżnicowa.nie implementacji kontenerów typu string..................................................................................................96 Zagadnienie 16. Powinieneś wiedzieć, jak przesyłać dan.e z kontenerów vector i string do klasycznego interfejsu zgodnego z C.........................................................102 Zagadnienie 17. Sztuczka programistyczna „swap trick” pozwa.lająca na obcięcie nadmiarowej pojemności .........................................................................106 Zagadnienie 18. Unikajmy stosowania wektora typu vector bool ...............................108 Rozdział 3. Kontenery asocjacyjne ...................................................n................ 111 Zagadnienie 19. Ten sam czy taki sam — zrozumienie różn.icy pomiędzy relacjami równości a równoważności .........................................................................112 Zagadnienie 20. Określajmy typy porównawcze dla konten.erów asocjacyjnych zawierających wskaźniki.............................................................................................117 Zagadnienie 21. Funkcje porównujące powinny dla dwóch dokład.nie równych wartości zawsze zwracać wartość false.......................................................................122 Zagadnienie 22. Unikajmy bezpośredniego modyfikowani.a klucza w kontenerach typu set i multiset .........................................................................................................126 Zagadnienie 23. Rozważmy zastąpienie kontenerów asocja.cyjnych posortowanymi wektorami..........................................................................................133 Zagadnienie 24. Gdy efektywność działania jest szczegól.nie istotna, należy bardzo uważnie dokonywać wyboru pomiędzy map::operator[]() a map::insert()................140 Zagadnienie 25. Zapoznaj się z niestandardowymi kontenerami mieszanymi ...............146 Rozdział 4. Iteratory ...................................................n..................................... 151 Zagadnienie 26. Lepiej wybrać iterator niż const_itera.tor, reverse_iterator czy const_reverse_iterator...........................................................................................151 Zagadnienie 27. Stosujmy funkcje distance() i advance(), by pr.zekształcić typ const_iterator na typ iterator .................................................................................155 Zagadnienie 28. Jak stosować metodę base() należącą do klasy reverse_iterator w celu uzyskania typu iterator?...................................................................................159 Zagadnienie 29. Rozważ zastosowanie iteratorów typu is.treambuf_iterator do wczytywania danych znak po znaku ......................................................................163 Rozdział 5. Algorytmy...................................................n................................... 165 Zagadnienie 30. Upewnijmy się, że docelowe zakresy są wystarczająco obszerne .......166 Zagadnienie 31. Pamiętajmy o dostępnych i stosowanych opcjach sortowania .............171 Zagadnienie 32. Stosujmy metodę erase() w ślad za algo.rytmami kategorii remove(), jeśli naprawdę chcemy coś skutecznie usunąć z kontenera .......................................177 Zagadnienie 33. Przezornie i ostrożnie stosujmy algo.rytmy kategorii remove() wobec kontenerów zawierających wskaźniki .............................................................182 Zagadnienie 34. Zwracajmy uwagę, które z algorytmów ocze.kują posortowanych zakresów ............................................................................................186 Spis treści 7 Zagadnienie 35. Implementujmy zwykłe porównywanie łańcuchów z.naków bez rozróżniania wielkości liter za pomocą mismatch() lub lexicographical_compare() ....190 Zagadnienie 36. Zrozum prawidłową implementację algorytmu copy_if()....................196 Zagadnienie 37. Stosujmy accumulate() lub for_each() do operacji. grupowych na zakresach .................................................................................................................198 Rozdział 6. Funktory, klasy-funktory, funkcje i inne........................................... 205 Zagadnienie 38. Projektowanie klas-funktorów do przekazywania przez wartość ........205 Zagadnienie 39. Predykaty powinny być funkcjami czystymi .......................................208 Zagadnienie 40. Klasy-funktory powinny być adaptowalne...........................................212 Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref?................215 Zagadnienie 42. Upewnij się, że less t () oznacza operator () .....................................219 Rozdział 7. Programowanie za pomocą biblioteki STL ....................................... 223 Zagadnienie 43. Używaj algorytmów, zamiast pisać pętle .............................................223 Zagadnienie 44. Zamiast algorytmów stosujmy metody o takich samych nazwach ......231 Zagadnienie 45. Rozróżnianie funkcji count(), find(), binary_se.arch(), lower_bound(), upper_bound() i equal_range() ..........................................................233 Zagadnienie 46. Jako parametry algorytmów stosuj funktory, a nie funkcje .................241 Zagadnienie 47. Unikaj tworzenia kodu „tylko do zapisu” ............................................245 Zagadnienie 48. Zawsze dołączaj właściwe pliki nagłówkowe ......................................248 Zagadnienie 49. Naucz się odczytywać komunikaty kompilat.ora związane z biblioteką STL ...........................................................................................................249 Zagadnienie 50. Poznaj strony WWW związane z biblioteką STL ................................256 Dodatek A Bibliografia ...................................................n................................. 263 Książki napisane przeze mnie ..........................................................................................263 Książki, które nie ja napisałem (choć chciałbym)............................................................264 Dodatek B Porównywanie ciągów znaków bez uwzględniania wielkości liter ..... 267 Jak wykonywać porównywanie ciągów znaków bez uwzględniani.a wielkości liter — artykuł autorstwa Matta Austerna ..........................................................................267 Dodatek C Uwagi na temat platformy STL Microsoftu ...................................... 277 Szablony metod w STL ....................................................................................................277 MSVC wersje 4 do 6 .........................................................................................................278 Rozwiązania dla kompilatorów MSVC w wersji 4 do 5.................................................279 Dodatkowe rozwiązanie dla kompilatora MSVC w wersji 6..........................................280 Skorowidz...................................................n................................... 283 Rozdział 6. Funktory, klasy-funktory, funkcje i inne Czy nam się to podoba czy nie, funkcje i podobne do funkcji obiekty — funktory — są częścią STL. Kontenery asocjacyjne stosują je do porządkowania swoich elemen- tów, w algorytmach typu HKPFAKH wykorzystywane są do kontroli zachowań tych al- gorytmów. Algorytmy takie jak HQTAGCEJ i VTCPUHQTO są bez funktorów zupełnie nieprzydatne, natomiast adaptory typu PQV i DKPFPF służą do tworzenia funktorów. Wszędzie gdzie spojrzeć, można w STL znaleźć funktory i klasy-funktory. Znajdą się one również w Twoich kodach źródłowych. Efektywne zastosowanie STL bez umiejęt- ności tworzenia dobrych funktorów jest po prostu niemożliwe. Z tego powodu większa część tego rozdziału będzie opisywać sposoby takiego tworzenia funktorów, aby zacho- wywały się one zgodnie z wymaganiami STL. Jednak jedno z zagadnień opisuje zupeł- nie inny temat. Ten podrozdział spodoba się osobom, które zastanawiały się, dlaczego konieczne jest zaśmiecanie kodu programu wywołaniami funkcji RVTAHWP , OGOAHWP i OGOAHWPATGH . Oczywiście można zacząć lekturę od tego właśnie podrozdziału, „Za- gadnienie 41.”, ale proszę nie poprzestawać na nim. Po poznaniu przedstawionych tam funkcji konieczne będzie zapoznanie się z informacjami z pozostałych, dzięki czemu Twoje funkcje będą prawidłowo działały zarówno z tymi fuęnkcjami, jak i z resztą STL. Zagadnienie 38. Projektowanie klas-funktorów do przekazywania przez wartość Zagadnienie 38. Projektowanie klas-funktorów do przekazywania przez wartość Języki C i C++ nie pozwalają na przekazywanie funkcji w parametrach innych funkcji. Konieczne jest przekazywanie wskaźników na te funkcje. Na przykład poniżej znajduje się deklaracja funkcji SUQTV z biblioteki standardowej. 206 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne XQKFSUQTV XQKF DCUGUKGAVPOGODUKGAVUKG KPV EORHEP EQPUVXQKF EQPUVXQKF  „Zagadnienie 46.” wyjaśnia, dlaczego algorytm UQTV jest zazwyczaj lepszym wy- borem od funkcji SUQTV . Teraz omówimy sposób deklarowania parametru EORHEP w funkcji SUQTV . Po dokładniejszym przyjrzeniu się wszystkim gwiazdkom okazuje się, że argument EORHEP, który jest wskaźnikiem na funkcję, jest kopiowany (czyli prze- kazywany przez wartość) z miejsca wywołania do funkcji SUQTV . Jest to typowy przy- kład zasady stosowanej w bibliotekach standardowych języków C i C++, czyli prze- kazywania przez wartość wskaźników na funkcje. W STL obiekty funkcyjne modelowane są podobnie do wskaźników na funkcje, wobec czego w STL obiekty funkcyjne są również przekazywane do i z funkcji przez war- tość. Najlepszym przykładem może być deklaracja funkcji HQTAGCEJ algorytmu po- bierającego i zwracającego przez wartość funktory. VGORNCVGENCUU+PRWV+VGTCVQT ENCUU(WPEVKQP (WPEVKQPYTCECPCRTGYCTVQħè HQTAGCEJ +PRWV+VGTCVQTHKTUV +PRWV+VGTCVQTNCUV (WPEVKQPH RTGMC[YCPCRTGYCTVQħè Tak na prawdę przekazywanie przez wartość nie jest absolutnie wymagane, ponie- waż w wywołaniu funkcji HQTAGCEJ można wyraźnie zaznaczyć typy parametrów. Na przykład w poniższym kodzie funkcja HQTAGCEJ pobiera i zwraca funktory przez referencję. ENCUU Q5QOGVJKPI RWDNKEWPCT[AHWPEVKQPKPVXQKF ]YRQFTQFKCNG CICFPKGPKG QRKUCPC QUV CPKGMNCUCDCQYC XQKFQRGTCVQT KPVZ ]ŗ_  _ V[RGFGHFGSWGKPV KVGTCVQT GSWG+PV+VGTY[IQFPCFGMNCTCELCV[RGFGH FGSWGKPV FK  Q5QOGVJKPIFWVYQT GPKGHWPMVQTC  HQTAGCEJ GSWG+PV+VGTY[YQđCPKGHWPMELKHQTAGCEJ  Q5QOGVJKPI FKDGIKP RCTCOGVTCOKV[RW GSWG+PV+VGT FKGPF K Q5QOGVJKPI F YVGPURQUÎDFOWUKD[èRTGMCCPC KY TÎEQPCRTGTGHGTGPELú Użytkownicy STL prawie nigdy nie przeprowadzają tego typu operacji, co więcej, pewne implementacje niektórych algorytmów STL nie skompilują się w przypadku przekazywania funktorów przez referencję. W pozostałej części tego sposobu będę za- kładał, że funktory można przekazywać wyłącznie przez wartość, co w praktyce jest niemal zawsze prawdą. Funktory muszą być przekazywane przez wartość, dlatego to na Tobie spoczywa ciężar sprawdzenia, czy Twoje funktory przekazywane w ten sposób zachowują się prawidło- wo. Implikuje to dwie rzeczy. Po pierwsze, muszą one być małe, bo kopiowanie dużych Zagadnienie 38. Projektowanie klas-funktorów do przekazywania przez wartość 207 obiektów jest bardzo kosztowne. Po drugie, Twoje funktory nie mogą być polimor- ficzne (muszą być monomorficzne), co oznacza, że nie mogą używać funkcji wirtual- nych. Wynika to z faktu, że obiekt klasy pochodnej przekazywany w parametrze typu klasy bazowej zostaje obcięty, czyli w czasie kopiowania usunięte będą z niego odziedziczone elementy. W podrozdziale „Zagadnienie 3.” opisano inny problem, jaki rozczłonkowanie (ang. slicing) tworzy w czasie stosowania biblioteki STL. Unikanie rozczłonkowania obiektów i uzyskiwanie dobrej wydajności są oczywiście bardzo ważne, ale trzeba pamiętać, że nie wszystkie funkcje mogą być małe i nie wszy- stkie obiekty mogą być monomorficzne. Jedną z zalet funktorów w porównaniu ze zwykłymi funkcjami jest fakt, że mogą one przechowywać informację o swoim stanie. Niektóre funktory muszą być duże, dlatego tak ważna jest możliwość przekazywania takich obiektów do algorytmów STL z równą łatwością jak obięektów niewielkich. Zakaz używania funktorów polimorficznych jest równie nierealistyczny. Język C++ pozwala na tworzenie hierarchii dziedziczenia i dynamicznego wiązania. Te możli- wości są tak samo użyteczne w czasie tworzenia klas-funktorów, jak i w innych miej- scach. Klasy-funktory bez dziedziczenia byłyby jak C++ bez „++”. Oczywiście ist- nieje sposób umożliwiający tworzenie dużych i polimorficznych obiektów funkcyjnych, a jednocześnie umożliwiający przekazywanie ich przez wartość, zgodnie z konwencją przyjętą w STL. Ten sposób wymaga przeniesienia wszystkich danych i funkcji polimorficznych do in- nej klasy, a następnie w klasie-funktorze umieszczenie wskaźnika na tę nową klasę. Na przykład utworzenie klasy polimorficznej zawierająceęj duże ilości danych: VGORNCVGV[RGPCOG6 ENCUU$2( $2( $KI2QN[OQT RJKE RWDNKE(W PEVQT NCUUŌ œ9K GNMC2QNKHQTOKEPC -N CUC(WPMVQTŒ WPCT[AHWPEVKQP6XQKF ]6CMNCUCDCQY CQUVCPKG QDLCħPKQPC YRQFTQFKCNG CICFPKGPKG RTKXCVG 9KFIGVY6CMNCUCCYKGTCYKGNGFCP[EJ KPVZFNCVGIQRT GMC[YCPKGKEJRTG YCTVQħèD[đQD[P KGGHGMV[YPG RWDNKE XKTVWCNXQKFQRGTCVQT EQPUV6XCN EQPUV VQLGUVHWPMELCYKTVWCNPC FNCVGIQ  TQEđQPMQYCPKGļNGPCPKæ YRđ[PKG _ wymaga utworzenia małej monomorficznej klasy zawierającej wskaźnik na klasę implementacji i umieszczenie w klasie implementacji wszystkich danych i funkcji wirtualnych: VGORNCVGV[RGPCOG6 MNCUCKORNGOGPVCELKFNC ENCUU$2( +ORN RWDNKEWPCT[AHWPEVKQP6XQKF ]OQF[HKMQYCPGLMNCU[$2( RTKXCVG 9KFIGVYRTGPKGUKQPQVWVCLYU [UVMKGFCPG KPVZCYCTVGFQVGLRQT [YMNCUKG$2( 208 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne XKTVWCN`$2( +ORN MNCU[RQNKOQTHKEPGOWUæOKGè YKTVWCNPGFGUVTWMVQT[ XKTVWCNXQKFQRGTCVQT EQPUV6XCN EQPUV HTKGPFENCUU$2( 6 MNCUC$2( DúFKGOKCđCFQUVúRFQFC P[EJ _ VGORNCVGV[RGPCOG6 ENCUU$2( PKGYKGNMCOQP QOQTHKEPCYGTULC RWDNKEWPCT[AHWPEVKQP6XQKF ]MNCU[$2( RTKXCVG $2( +ORN6  R+ORNVQUæYU[UVMKGFCPGRTGEJQY[YCPG RTGMNCUú$2( RWDNKE XQKFQRGTCVQT EQPUV6XCN EQPUVVCPKGYKTVWCNPCOGVQFCRTGMCWLG ]Y[YQđCPKGFQMN CU[$2( +ORN R+ORN QRGTCVQT XCN  _  _ Funkcja QRGTCVQT w klasie $2( przedstawia sposób implementacji wszystkich niemal wirtualnych funkcji w tej klasie. Wywołują one swoje wirtualne odpowiedniki z klasy $2( +ORN. W efekcie otrzymujemy małą i monomorficzną klasę $2( , która ma dostęp do dużej ilości danych i zachowuje się jak klasaę polimorficzna. Pomijam tutaj pewne szczegóły, ponieważ podstawa naszkicowanej tu techniki jest do- brze znana programistom C++. Opisywana jest ona w książce Effective C++ w roz- dziale 34. W książce Design Patterns [6] nazywana jest Bridge Pattern, natomiast Sut- ter w swojej książce Exceptional C++ [8] nazywa ją Idiomem Pimpl. Z punktu widzenia STL podstawową rzeczą, o której należy pamiętać, jest fakt, że klasy stosujące tę technikę muszą rozsądnie obsługiwać kopiowanie. Autor opisywanej wyżej klasy $2( musiałby odpowiednio zaprojektować jej konstruktor kopiujący, tak aby pra- widłowo obsługiwał wskazywany przez klasę obiekt $2( +ORN. Najprawdopodobniej najprostszym sposobem będzie zliczanie referencji do niego, podobnie jak w przypadku szablonu UJCTGFARVT, o którym można przeczytać w podrozdziale „Zagadnienie ę50.”. Tak naprawdę, na potrzeby tego podrozdziału należy jedynie zadbać o właściwe zacho- wanie konstruktora kopiującego. W końcu funktory są zawsze kopiowane (czyli prze- kazywane przez wartość) w momencie przekazywania ich do i z funkcji, a to oznacza dwie rzeczy: muszą być małe i monomorficzne. Zagadnienie 39. Predykaty powinny być funkcjami czystymi Zagadnienie 39. Predykaty powinny być funkcjami czystymi Obawiam się, że będziemy musieli zacząć od zdefiniowanięa pewnych pojęć. Zagadnienie 39. Predykaty powinny być funkcjami czystymi 209  Predykat to funkcja zwracająca wartość typu DQQN (lub inną dającą się łatwo przełożyć na DQQN). Predykaty są funkcjami często wykorzystywanymi w STL. Są to, na przykład, funkcje porównujące w standęardowych kontenerach asocjacyjnych; wykorzystują je również (pobięerają jako parametry) różnego rodzaju algorytmy sortujące, a takżeę algorytmy typu HKPFAKH . Opis algorytmów sortujących znajduje się w podrozdzialeę „Zagadnienie 31.”.  Funkcje czyste to funkcje, których wartość zwracana zależy wyłącznie od wartości jej parametrów. Jeżeli H jest funkcją czystą, a Z i [ są obiektami, to wartość zwracana przez tę funkcję może zmienić się ęwyłącznie w przypadku zmiany w obiekcie Z lub [. W języku C++ dane, których używa funkcja czysta, są alboę przekazywane jako parametry albo pozostają niezmienne w czasie życęia funkcji (oznacza to, że muszą być zadeklarowane jako EQPUV). Jeżeli funkcja czysta wykorzystywałaby dane zmieniające się między poszczegęólnymi jej wywołaniami, wtedy kolejne wywołania z tymi samymi parametrami mogłyby zwracać różne wartości, a to byłoby niezgodne z definicęją funkcji czystej. Powinno to wystarczyć do wyjaśnienia dlaczego predykaty powinny być funkcjami czystymi. Teraz muszę jedynie Cię przekonać, że ta rada ma solidne podstawy. W zwią- zku z tym będę musiał wyjaśnić jeszcze jedno pojęcie.  Klasa-predykat jest klasą-funktorem, w której funkcja QRGTCVQT jest predykatem, co znaczy, że zwraca wartości VTWG lub HCNUG albo wartość, którą można bezpośrednio przekształcić na wartość logiczęną. Jak można się domyślić, w miejscach, w których STL oczekuje podanięa predykatu, można podać albo rzeczywisty predykat albo obiekt-predyękat. To wszystko. Teraz mogę zacząć udowadniać, że naprawdę warto przestrzegać porad przedstawionych w tym podrozdziale. W podrozdziale „Zagadnienie 38.” wyjaśniłem, że funktory przekazywane są przez wa- rtość, dlatego należy tak je budować, aby można je było łatwo kopiować. W przypadku funktorów będących predykatami istnieje jeszcze jeden powód takiego projektowania. Algorytmy mogą pobierać kopie funktorów i przechowywać je przez pewien czas, za- nim ich użyją. Jak można się spodziewać, implementacje niektórych algorytmów oczy- wiście korzystają z tej możliwości. Efektem takiego stanu rzeczy jest fakt, że funkcje predykatów muszą być funkcjami czystymi. Aby móc udowodnić te twierdzenia, załóżmy, że budując klasę nie zastosujemy się do nich. Przyjrzyjmy się poniższej (źle zaprojektowanej) klasie-predykatowi. Niezależnie od przekazywanych jej parametrów zwraca ona wartość VTWG tylko raz — przy trze- cim wywołaniu. W pozostałych przypadkach zwraca wartość HCNUG. ENCUU$CF2TGFKECVGYRQFTQFKCNG CICFPKGPKG PCLFKGU RWDNKEWPCT[AHWPEVKQP9KFIGVDQQN ]KPHQTOCELG PCVGOCVMNCU[DCQYGL RWDNKE $CF2TGFKECVG VKOGU CNNGF  ]_KPKELCNKCELCOKGPPGLVKOGU CNNGF DQQNQRGTCVQT EQPUV9KFIGV 210 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne ] TGVWTP VKOGU CNNGF _ RTKXCVG UKGAVVKOGU CNNGF _ Przypuśćmy, że chcielibyśmy użyć tej klasy do usunięcia trzeciego elementu wektora XGEVQT9KIFGV : XGEVQT9KFIGV XYWVYÎTYGMV QTKWOKGħèYPKO MKNMCGNGOGP VÎY XYGTCUG TGOQXGAKH XYDGIKP WUWēVTGEKGNGOGPV XYGPF YRQFTQFKCNG CICFPKGPKG PCLFKGU KPHQTOCELG FQV[EæEG $CF2TGFKECVG YKæMWHWPMELKGTCUG KTGOQXGAKH XYGPF  Powyższy kod wygląda zupełnie przyzwoicie, jednak w wielu implementacjach STL usunie on nie tylko trzeci, ale i szósty element wektora. Aby zrozumieć, jak jest to możliwe, dobrze jest poznać częsty sposób implementacji funkcji TGOQXGAKH . Należy pamiętać, że funkcja TGOQXGAKH nie musi być implemen- towana w ten sposób: VGORNCVGV[RGPCOG(YF+VGTCVQTV[RGPCOG2TGFKECVG (YF+VGTCVQTTGOQXGAKH (YF+VGTCVQTDGIKP(YF+VGTCVQTGPF2TGFKECVGR ] DGIKPHKPFAKH DGIKPGPFR  KH DGIKPGPF TGVWTPDGIKP GNUG] (YF+VGTCVQTPGZVDGIKP TGVWTPTGOQXGAEQR[AKH PGZVGPFDGIKPR  _ _ Szczegóły podanego kodu nie są istotne, jednak należy zwrócić uwagę, że predykat R jest przekazywany najpierw do funkcji HKPFAKH , a następnie do funkcji TGOQXGAEQ R[AKH . W obu przypadkach R jest przekazywany do tych algorytmów przez wartość (czyli kopiowany). Teoretycznie nie musi to być prawdą, jednak w praktyce najczęściej jest. Więcej informacji na ten temat znajdziesz w poędrozdziale „Zagadnienie 38.”. Początkowe wywołanie funkcji TGOQXGAKH (w kodzie klienta, związane z próbą usu- nięcia trzeciego elementu wektora XY) tworzy anonimowy obiekt klasy $CF2TGFKECVG zawierający wewnętrzną składową VKOGU CNNGF inicjowaną wartością zero. Ten obiekt (wewnątrz funkcji TGOQXGAKH nazywa się R) jest kopiowany do funkcji HKPFAKH , w związku z czym ona również otrzymuje obiekt klasy $CF2TGFKECVG z wyzerowaną zmienną VKOGU CNNGF. Funkcja HKPFAKH „wywołuje” ten obiekt tak długo, aż zwróci wartość VTWG, czyli trzykrotnie, a następnie przekazuje sterowanie do funkcji TGOQXGA KH . Funkcja TGOQXGAKH wznawia swoje działanie i w końcu wywołuje funkcję TGOQXGAEQR[AKH , przekazując jej kolejną kopię predykatu R. Jednak wartość zmiennej VKOGU CNNGF w obiekcie R ma nadal wartość zero. Funkcja HKPFAKH nigdy Zagadnienie 39. Predykaty powinny być funkcjami czystymi 211 nie wywoływała obiektu R, a jedynie jego kopię. W efekcie trzecie wywołanie przez funkcję TGOQXGAEQR[AKH podanego jej predykatu również zwróci wartość VTWG. Oto dlaczego funkcja TGOQXGAKH usunie z wektora XY dwa elementy zamiast jednego. Najprostszym sposobem na uniknięcie tego rodzaju problemów jest deklarowanie w klasach-predykatach funkcji QRGTCVQT jako EQPUV. W tak zadeklarowanych funk- cjach kompilator nie pozwoli zmienić wartości składników klasy: ENCUUDCF2TGFKECVG RWDNKEWPCT[AHWPEVKQP9KFIGVDQQN ] RWDNKE DQQNQRGTCVQT EQPUV9KFIGV EQPUV ] TGVWTP VKOGU CNNGFDđæFYHWPMELCEJV[RWEQPUV _PKGOQľPCO KGPKCèYCTVQħEKUMđCFQY[EJ  _ Ze względu na to, że opisany problem można rozwiązać w tak prosty sposób, byłem bliski nazwania tego zagadnienia „W klasach-predykatach stosujmy QRGTCVQT typu EQPUV”. Niestety, takie rozwiązanie nie jest wystarczające. Nawet funkcje skła- dowe oznaczone jako EQPUV mogą używać zmiennych pól klasy, niestałych lokal- nych obiektów statycznych, niestałych statycznych obiektów klas, niestałych obiektów w zakresie przestrzeni nazw i niestałych obiektów globalnych. Dobrze za- projektowana klasa-predykat gwarantuje, że funkcje jej operatora QRGTCVQT są nie- zależne od tego rodzaju obiektów. Zadeklarowanie QRGTCVQT jako EQPUV jest ko- nieczne dla uzyskania właściwego zachowania klasy, jednak nie jest wystarczające. Co prawda wystarczyłoby tak zmodyfikować składowe, żeby nie wpływały na wynik predykatu, jednak dobrze zaprojektowany QRGTCVQT wymaga czegoś więcej — musi być funkcją czystą. Zaznaczyłem już, że w miejscach, w których STL oczekuje funkcji predykatu, za- akceptowany zostanie również obiekt klasy-predykatu. Ta zasada obowiązuje również w przeciwnym kierunku. W miejscach, w których STL oczekuje obiektu klasy- predykatu, zaakceptowana zostanie również funkcja-predykat (prawdopodobnie zmody- fikowana przez funkcję RVTAHWP — zobacz „Zagadnienie 41.”). Zostało już udowod- nione, że funkcje QRGTCVQT w klasach-predykatach muszą być funkcjami czysty- mi, co w połączeniu z powyższymi stwierdzeniami oznacza, że funkcje-predykaty również muszą być funkcjami czystymi. Poniższa funkcja nie jest aż tak złym predy- katem jak obiekty tworzone na podstawie klasy $CF2TGFKECVG, ponieważ jej zastoso- wanie wiąże się z istnieniem tylko jednej kopii zmiennej stanu, jednak i ona narusza zasady tworzenia predykatów: DQQNCPQVJGT$CF2TGFKECVG EQPUV9KFIGVEQPUV9KFIGV ] UVCVKEKPVVKOGU CNNGF0KG0KG0KG0KG0KG0KG0KG0KG TGVWTP VKOGU CNNGF2TGF[MCV[RQYKPP[D[èHWPMELCOKE[UV[OK _CVCMKGHWPME LGPKGOCLæUVCPW Niezależnie od tego, w jaki sposób tworzysz swoje predykaty, powinny one zawsze być funkcjami czystymi. 212 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne Zagadnienie 40. Klasy-funktory powinny być adaptowalne Zagadnienie 40. Klasy-funktory powinny być adaptowalne Przypuśćmy, że mamy listę wskaźników 9KFIGV i funkcję określającą, czy dany wskaźnik identyfikuje interesujący nas element. NKUV9KFIGV YKFIGV2VTU DQQNKU+PVGTGUVKPI EQPUV9KFIGV RY  Jeżeli chcielibyśmy znaleźć pierwszy wskaźnik na interesujący nas element, można by to zrobić w prosty sposób: NKUV9KFIGV KVGTCVQTKHKPFAKH YKFIGV2VTUDGIKP YKFIGV2VTUGPF  KU+PVGTGUVKPI  KH KYKFIGV2VTUGPF ] QRGTCELGYK æCPGRKGTYU[O _KPVGTGUWLæE [OPCUYUMC[YCP[OGNGOGPVGO Jeżeli jednak będziemy chcieli uzyskać pierwszy wskaźnik na element nas nieinteresu- jący, ten najbardziej oczywisty sposób nawet się nie skomępiluje. NKUV9KFIGV KVGTCVQTK HKPFAKH YKFIGV2VTUDGIKP YKFIGV2VTUGPF  PQV KU+PVGTGUVKPI DđæFPKGUMQORKNWLGUKú W takim przypadku należy funkcję KU+PVGTGUVKPI przekazać najpierw do funkcji RVTAHWP , a dopiero potem do adaptora PQV . NKUV9KFIGV KVGTCVQTK HKPFAKH YKFIGV2VTUDGIKP YKFIGV2VTUGPF  PQV RVTAHWP KU+PVGTGUVKPI VCMLGUVFQDTG KH KYKFIGV2VTUGPF ] QRGTCELGYKæCPGRKGTYU[O _PKGKPVGTGUWLæE[OPCUYUMC[YC P[OGNGOGPVGO Tutaj rodzi się kilka pytań. Dlaczego konieczne jest zastosowanie funkcji RVTAHWP na funkcji KU+PVGTGUVKPI przed przekazaniem jej do PQV ? Co i jak robi funkcja RVTAHWP , że umożliwia skompilowanie powyższego kodu? Odpowiedź na te pytania jest nieco zaskakująca. Funkcja RVTAHWP udostępnia jedynie kilka deklaracji V[RGFGH. To wszystko. Są to deklaracje wymagane przez adaptor PQV i z tego powodu bezpośrednie przekazanie funkcji KU+PVGTGUVKPI do PQV nie bę- dzie działać. Symbol KU+PVGTGUVKPI jest jedynie prostym wskaźnikiem na funkcję, dlatego brakuje mu deklaracji wymaganych przez adaptor ęPQV . W STL znajduje się wiele innych komponentów tworzących podobne wymagania. Każ- dy z czterech podstawowych adaptorów funkcji (PQV , PQV , DKPFUV i DKPFPF ) wymaga istnienia odpowiednich deklaracji V[RGFGH, tak jak i wszystkie inne niestan- dardowe, ale zgodne z STL adaptory tworzone przez różne osoby (na przykład te two- rzone przez firmy SGI lub Boost — zobacz „Zagadnienie 50.”). Funktory zawierające te wymagane deklaracje V[RGFGH nazywane są adaptowalnymi, natomiast funkcje ich Zagadnienie 40. Klasy-funktory powinny być adaptowalne 213 nieposiadające nazywane są nieadaptowalnymi. Adaptowalnych funktorów można używać w znacznie większej ilości kontekstów niż nieadaptowalnych, dlatego, gdy tylko to możliwe, należałoby budować funktory adaptowalne. Taka operacja nie kosz- tuje wiele, a może znacznie ułatwić pracę użytkownikom Twoich klas-funktorów. Zapewne już się denerwujesz, że cały czas mówię o „odpowiednich deklaracjach V[ RGFGH”, ale nigdy nie określam, jakie to deklaracje. Są to deklaracje: CTIWOGPVAV[RG, HKTUVACTIWOGPVAV[RG, UGEQPFACTIWOGPVAV[RG i TGUWNVAV[RG. Niestety, życie nie jest całkiem proste, ponieważ w zależności od rodzaju klasy-funktory powinny udostęp- niać różne zestawy tych nazw. Tak naprawdę, jeżeli nie tworzysz własnych adapto- rów (a tego w tej książce nie będziemy opisywać), nie musisz znać tych deklaracji. Wynika to z faktu, że najprostszym sposobem udostępnienia tych deklaracji jest odziedziczenie ich po klasie bazowej, a właściwie po bazowej strukturze. Klasy- funktory, w których QRGTCVQT przyjmuje jeden argument, powinny być wywo- dzone z UVFWPCT[AHWPEVKQP, natomiast klasy-funktory, w których QRGTCVQT przyjmuje dwa argumenty, powinny być wywodzone z UVFDKPCT[AHWPEVKQP. Należy pamiętać, że WPCT[AHWPEVKQP i DKPCT[AHWPEVKQP to szablony, dlatego nie moż- na bezpośrednio po nich dziedziczyć, ale dziedziczyć po wygenerowanych przez nie strukturach, a to wymaga określenia typów argumentów. W przypadku WPCT[AHWPEVKQP musisz określić typ parametru pobieranego przez QRGTCVQT Twojej klasy funkto- ra, a także typ jego wartości zwracanej. W przypadku DKPCT[AHWPEVKQP konieczne jest określenie trzech typów: pierwszego i drugiego parametru QRGTCVQT oraz zwraca- nej przez niego wartości. Poniżej podaję kilka przykładów: VGORNCVGV[RGPCOG6 ENCUU/GGVU6JTGUJQNFRWDNKEUVFWPCT[AHWPEVKQP9KFIGVDQQN ] RTKXCVG EQPUV6VJTGUJQNF RWDNKE /GGVU6JTGUJQNF EQPUV6VJTGUJQNF  DQQNQRGTCVQT EQPUV9KFIGV EQPUV  _ UVTWEV9KFIGV0COG QORCTG UVFDKPCT[AHWPEVKQP9KFIGV9KFIGVDQQN ] DQQNQRGTCVQT EQPUV9KFIGVNJUEQPUV9KFIGVTJU EQPUV _ Proszę zauważyć, że w obydwu przypadkach typy przekazywane do WPCT[AHWPEVKQP i DKPCT[AHWPEVKQP są identyczne z typami pobieranymi i zwracanymi przez QRGTC VQT danej klasy funktora. Troszkę dziwny jest tylko sposób przekazania typu warto- ści zwracanej przez operator jako ostatniego parametru szablonów WPCT[AHWPEVKQP lub DKPCT[AHWPEVKQP. Zapewne nie uszło Twojej uwadze, że /GGVU6JTGUJQNF jest klasą, a 9KFIGV0COG QORC TG jest strukturą. Wynika to z faktu, że /GGVU6JTGUJQNF ma składowe opisujące jej wewnętrzny stan (pole VJTGUJQNF), dlatego naturalną rzeczą jest zastosowanie w takiej sytuacji klasy. Z kolei 9KFIGV0COG QORCTG nie przechowuje informacji o stanie, dlatego 214 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne nie ma potrzeby ukrywania w niej jakichkolwiek danych. Autorzy klas, w których nie ma elementów prywatnych, często deklarują takie klasy jako struktury. Prawdopo- dobnie chodzi o możliwość uniknięcia wpisywania w takiej klasie słowa kluczowego RWDNKE. Wybór deklaracji takich klas jako klasy lub struktury zależy wyłącznie od prefe- rencji programisty. Jeżeli cały czas próbujesz wykuć własny styl, a chciałbyś naślado- wać zawodowców, zauważ, że w bibliotece STL wszystkie klasy nieposiadające stanu (na przykład NGUU6 , RNWU6 itd.) deklarowane są jako struktury. Przyjrzyjmy się jeszcze raz strukturze 9KFIGV0COG QORCTG: UVTWEV9KFIGV0COG QORCTG RWDNKEUVFDKPCT[AHWPEVKQP9KFIGV9KFIGVDQQN ] DQQNQRGTCVQT EQPUV9KFIGVNJUEQPUV9KFIGVTJU EQPUV _ Typ przekazywany do szablonu DKPCT[AHWPEVKQP to 9KFIGV, mimo że QRGTCVQT pobiera argumenty typu EQPUV 9KFIGV. Zazwyczaj niebędące wskaźnikami typy prze- kazywane do szablonu WPCT[AHWPEVKQP lub DKPCT[AHWPEVKQP odzierane są ze znaczni- ków EQPUV i referencji. (Nie pytaj dlaczego. Powód nie jest ani dobry, ani interesują- cy. Jeżeli jednak nadal bardzo chcesz wiedzieć, napisz program testowy i nie usuwaj w nim tych znaczników, a następnie przeanalizuj wynik działania kompilatora. Jeżeli po tym wszystkim nadal będziesz zainteresowany tematem, zajrzyj na stronę boost.org (zobacz „Zagadnienie 50.”) i przejrzyj na niej teksty dotyczące adaptorów funktorów i cech wywołań). W przypadku gdy QRGTCVQT pobiera wskaźniki jako parametry, opisane wyżej za- sady ulegają zmianie. Poniżej podaję strukturę podobną do 9KFIGV0COG QORCTG, która posługuje się wskaźnikami 9KFIGV : UVTWEV2VT9KFIGV0COG QORCTG RWDNKEUVFDKPCT[AHWPEVKQPEQPUV9KFIGV EQPUV9KFIGV DQQN ] DQQNQRGTCVQT EQPUV9KFIGV NJUEQPUV9KFIGV TJU EQPUV _ W tym przypadku typy przekazywane do DKPCT[AHWPEVKQP są identyczne z typami pobieranymi przez QRGTCVQT . Wszystkie klasy-funktory pobierające lub zwracające wskaźniki obowiązuje zasada nakazująca przekazywanie do WPCT[AHWPEVKQP lub DKPC T[AHWPEVKQP dokładnie takich samych typów, jakie pobiera lub zwracaę QRGTCVQT . Nie możemy zapomnieć, z jakiego powodu snujemy te opowieści o klasach bazowych WPCT[AHWPEVKQP i DKPCT[AHWPEVKQP — dostarczają one deklaracji V[RGFGH wymaga- nych przez adaptory funktorów, dlatego dziedziczenie po tych klasach pozwala two- rzyć funktory adaptowalne. To z kolei pozwala na pisanięe tego rodzaju rzeczy: NKUV9KFIGV YKFIGVU  NKUV9KFIGV TGXGTUGAKVGTCVQTKPC LFWLGQUVCVPKGNGOGPV HKPFAKH YKFIGVUTDGIKP YKFIGVUTGPF MVÎT[ PKGRTGMTQE[đ PQV /GGVU6TGUJQNFKPV  RTQIWQYCTVQħEK  EQMQNYKGMVQPCE[ 9KFIGVY CTIWOGPV[MQPUVTWMVQTC  NKUV9KFIGV KVGTCVQTKPC LFWLGRKGTYU[GNGOGPV HKPFAKH YKFIGVUDGIKP YKFIGVUGPF PCLFWLæE[UKúRTGFY Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref? 215 DKPFPF 9KFIGV0COG QORCTG Y YRQTæFMWUQTVQYCPKC  FGHKPKQYCP[ORTG 9KFIGV0COG QORCTG Gdyby nasze klasy-funktory nie zostały wywiedzione z klasy WPCT[AHWPEVKQP lub DKPC T[AHWPEVKQP, powyższe przykłady nawet by się nie skompilowały, ponieważ funkcje PQV i DKPFPF działają tylko z funktorami adaptowalnymi. W STL funktory modelowane są podobnie do funkcji w języku C++, które mają tylko jeden zestaw typów parametrów i jedną wartość zwracaną. W efekcie przyjmuje się, że każda klasa-funktor ma tylko jedną funkcję QRGTCVQT , której parametry i wartość zwracana powinny zostać przekazane do klas WPCT[AHWPEVKQP lub DKPCT[AHWPEVKQP (wynika to z omówionych właśnie zasad dla typów wskaźnikowych i referencyjnych). A z tego wynika z kolei, że nie powinno się łączyć funkcjonalności struktur 9KFIGV 0COG QORCTG i 2VT9KFIGV0COG QORCTG przez utworzenie jednej klasy mającej dwie funkcje QRGTCVQT . Jeżeli utworzysz taką klasę, będzie ona adaptowalna tylko w jednej wersji (tej zgodnej z parametrami przekazywanymi do DKPCT[AHWPEVKQP). Jak można się domyślać, funktor adaptowalny tylko w połowie równie dobrze mógłby nie być adaptowalny w ogóle. W niektórych przypadkach utworzenie możliwości wywołania funktora w wielu for- mach (a tym samym rezygnacja z adaptowalności) ma sens, co opisano w zagadnie- niach: 7., 20., 23. i 25. Należy jednak pamiętać, że tego rodzaju funktory są jedynie wyjątkami od zasady. Adaptowalność to cecha, do której należy dążyć w czasie two- rzenia klas-funktorów. Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref? Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref? O co chodzi z tymi funkcjami? Czasami trzeba ich używać, czasami nie. Co one wła- ściwie robią? Wygląda na to, że czepiają się nazw funkcji jak rzep psiego ogona. Nie- łatwo je wpisać, denerwują w czasie czytania i trudno je zrozumieć. Czy są to artefakty podobne do przedstawionych w podrozdziałach „Zagadnienie 10.” i „Zagadnienie 18.”, czy może członkowie komitetu standaryzacyjnego wycęięli nam niemiły dowcip? Spokojnie, te funkcje mają do spełnienia naprawdę ważne zadania i z całą pewno- ścią nie są dziwacznymi żartami. Jednym z ich podstawowych zadań jest zamaskowanie pewnych niekonsekwencji syntaktycznych języka C++. Jeżeli, posiadając funkcję H i obiekt Z, chcielibyśmy wywołać H na rzecz Z i jeste- śmy poza funkcjami składowymi obiektu Z, język C++ pozwala na zastosowanie trzech różnych składni takiego wywołania. H Z 5MđCFPKCPT5VQUQYCPCYRT[RCFMW IF[HWPMELC HPKGLGUVOGVQFæQDKGMVWZ ZH 5MđCFPKCPT5VQUQYCPCYRT[RCFMW 216 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne IF[HWPMELCHLGUVOGVQFæQDKGMVW CZLGUVQ DKGMVGONWDTGHGTGPELæFQ QDKGMVW R H 5MđCFPKCPT5VQUQYCPCYRT[RCFMW IF[HWPMELCHLGUVOGVQFæQDKGMVW CRLGUV YUMCļPKMKGOPCQDKGMVZ Teraz załóżmy, że mamy funkcję sprawdzającą elementy: XQKFVGUV 9KFIGVY URTCYFCY KLGľGNKPKGCNKE[QP VGUVW QPCECI QLCMQPKGYđCħEKY[ i kontener przechowujący te elementy: XGEVQT9KFIGV XYXYRTGEJQY WLGGNGOGPV[ Jeżeli chcielibyśmy sprawdzić wszystkie elementy w XY, moglibyśmy w prosty sposób wykorzystać funkcję HQTAGCEJ : HQTAGCEJ XYDGIKP XYGPF VGUV 9[YQđCPKGPT UMQORKNWLGUKú Wyobraźmy sobie, że VGUV nie jest zwyczajną funkcją, ale funkcją składową klasęy 9KIFGV, co oznacza, że obiekty tej klasy mogą same się spraęwdzać: ENCUU9KFIGV] RWDNKE  XQKFVGUV Y[MQPWLGUCOQURTCYFG PKGLGľGNK QDKGMV VJKUPKGRTGLF KGVGUVW _QPCECP[LGUVLCMQPK GYđCħEKY[ W świecie doskonałym moglibyśmy zastosować funkcję HQTAGCEJ , aby wywołać funkcję 9KFIGVVGUV dla każdego obiektu wektora XY: HQTAGCEJ XYDGIKP XYGPF  9KFIGVVGUV 9[YQđCPKGPT PKGUMQORKNWLGUKú Jeżeli świat byłby naprawdę doskonały, moglibyśmy również zastosować funkcję HQTAGCEJ , żeby wywołać funkcję 9KFIGVVGUV w elementach kontenera prze- chowującego wskaźniki 9KFIGV : NKUV9KFIGV NRYNRYRTGEJQ YWLGYUMCļPKMKPCGNGOGPV[ HQTAGCEJ NRYDGIKP NRYGPF  9KFIGVVGUV 9[YQđCPKGPT TÎYPKGľUKúPKG UMQORKNWLG Pomyślmy jednak, co by się działo w tym świecie doskonałym. W przypadku wywoła- nia nr 1 wewnątrz funkcji HQTAGCEJ wywoływalibyśmy zwykłą funkcję, przekazując jej obiekt, czyli konieczne byłoby zastosowanie składni nr 1. W przypadku wywoła- nia nr 2 wewnątrz funkcji HQTAGCEJ wywoływalibyśmy metodę pewnego obiektu, czyli konieczne byłoby zastosowanie składni nr 2. Natomiast w przypadku wywołania nr 3 wewnątrz funkcji HQTAGCEJ wywoływalibyśmy metodę obiektu, do którego od- wołujemy się poprzez wskaźnik, czyli konieczne byłoby zastosowanie składni nr 3. To wszystko oznacza, że musiałyby istnieć trzy różne wersje funkcji HQTAGCEJ , a świat nie byłby już tak doskonały. Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref? 217 W świecie rzeczywistym istnieje tylko jedna wersja funkcji HQTAGCEJ . Zapewne nie- trudno się domyślić, jak wygląda jej implementacja: VGORNCVGV[RGPCOG+PRWV+VGTCVQTV[RGPCOG(WPEVKQP (WPEVKQPHQTAGCEJ +PRWV+VGTCVQTDGIKP+PRWV+VGTCVQTGPF(WPEVKQPH ] YJKNG DGIKPGPF H DGIKP  _ Proszę zauważyć, że funkcja HQTAGCEJ wykorzystuje do wywoływania funkcji H składnię nr 1. Jest to ogólnie przyjęta w STL konwencja, funkcje i funktory są wywo- ływane za pomocą składni stosowanej dla zwykłych funkcji. To wyjaśnia, dlaczego można skompilować składnię nr 1, ale składni nr 2 i 3 już nie. Wszystkie algorytmy STL (w tym również HQTAGCEJ ) wykorzystują składnię nr 1, wobec czego jedynie wywoła- nie nr 1 jest z nią zgodne. Teraz powinno być już jasne, dlaczego istnieją funkcje OGOAHWP i OGOAHWPATGH . Sprawiają one, że funkcje składowe (które powinny być wywoływane za pomocą składni nr 2 lub 3) są wywoływane za pomocą składni nr 1. Funkcje OGOAHWP i OGOAHWPATGH wykonują swoje zadania w bardzo prosty sposób, spojrzenie na deklarację jednej z nich powinno całkowicie wyjaśnić zagadkę. Tak na- prawdę są to szablony funkcji, istnieje ich kilka wersji różniących się ilością parame- trów i tym, czy przystosowywana funkcja jest oznaczona jako EQPUV czy nie. Aby po- znać sposób działania tych funkcji, wystarczy zobaczyć ękod jednej z nich: VGORNCVGV[RGPCOG4V[RGPCOG FGMNCTCELCHWP MELKOGOAHWPFNC PKGQPCEQP[EJ OGOAHWPAV4 LCMQEQPUVHWPMELKUMđCFQY[EJ OGOAHWP 4  ROH PKGRQDKGTCLæE[EJľCFP[E JRCTCOGVTÎY  VQMNCUC C4VQYCTVQħèYTCECPC YUMC[YCPG LHWPMELKUMđCFQYGL Funkcja OGOAHWP pobiera wskaźnik na metodę (ROH) i zwraca obiekt typu OGOAHWPAV. Jest to klasa-funktor przechowująca wskaźnik na metodę i udostępniająca QRGTC VQT wywołujący tę metodę na rzecz obiektu podanego jako parametr tego opera- tora. Na przykład w kodzie: NKUV9KFIGV NRYRQFQDPKGLC MY[ľGL  HQTAGCEJ NRYDGIKP NRYGPF  OGOAHWP 9KFIGVVGUV VGTCUKúUMQORKNWLG funkcja HQTAGCEJ otrzymuje obiekt typu OGOAHWPAV przechowujący wskaźnik na funk- cję 9KFIGVVGUV. Dla każdego wskaźnika 9KFIGV z NRY za pomocą składni nr 1 wy- woływany jest obiekt OGOAHWPAV, a ten natychmiast wywołuje funkcję 9KFIGVVGUV zgodnie ze składnią nr 3. Ogólnie, funkcja OGOAHWP przystosowuje składnię nr 3 wymaganą przy wywołaniach funkcji 9KFIGVVGUV za pomocą wskaźnika 9KFIGV na składnię nr 1 stosowaną przez funkcję HQTAGCEJ , wobec czego nie powinno dziwić, że klasy w rodzaju OGOAHWPAV nazywane są adaptorami obiektów funkcyjnych. W podobny sposób funkcja OGOAHWPA TGH przystosowuje składnię nr 2, generując obiekty-adaptory typu OGOAHWPATGHAV. 218 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne Obiekty tworzone przez funkcje OGOAHWP i OGOAHWPATGH pozwalają nie tylko zakła- dać, że wszystkie funkcje wywoływane są za pomocą tej samej składni, ale również, podobnie jak obiekty generowanie przez funkcję RVTAHWP , udostępniają odpowiednie deklaracje V[RGFGH. Na temat tych deklaracji była mowa w podrozdziale „Zagadnienie 40.”. Dzięki tym wyjaśnieniom powinno być już jasne, dlacęzego ten kod się skompiluje: HQTAGCEJ XYDGIKP XYGPF VGUV Y[YQđCPKGPTUMQORKNWLGUKú a te nie: HQTAGCEJ XYDGIKP XYGPF 9KFIGVVGUV Y[YQđCPKGPTPKGUMQORKNWLG UKú HQTAGCEJ NRYDGIKP NRYGPF 9KFIGVVGUV Y[YQđCPKGPTPKGUMQORKNWLG UKú Wywołanie nr 1 przekazuje w parametrze funkcję, dlatego nie ma konieczności dosto- sowania składni do wymagań funkcji HQTAGCEJ . Algorytm wywoła otrzymaną funk- cję za pomocą właściwej składni. Co więcej, funkcja HQTAGCEJ nie używa żadnej z deklaracji V[RGFGH udostępnianej przez funkcję RVTAHWP , więc nie ma potrzeby przekazywania funkcji VGUV za pośrednictwem tej funkcji. Z drugiej strony, udo- stępnienie tych deklaracji na nic nie wpłynie, więc poniższy kod zadziała tak samo jak ten podany wyżej. HQTAGCEJ XYDGIKP XYGPF RVTAHWP VGUV UMQORKNWLGUKúKCFKCđC  RQFQDPKGLCMY[YQđCPKGPT Jeżeli teraz już nie wiesz, kiedy stosować funkcję RVTAHWP , a kiedy nie — możesz używać jej przy każdym przekazywaniu funkcji do komponentu STL. Bibliotece nie zrobi to żadnej różnicy, nie wpłynie też na wydajność programu. Najgorsze, co może Ci się zdarzyć, to zdziwienie na twarzy osoby czytającej Twój kod pojawiające się w momencie napotkania nadmiarowego wywołania funkcji RVTAHWP . Na ile będzie Ci to przeszkadzać? Chyba zależy to od Twojej wrażliwości ęna zdziwione twarze. Inną strategią dotyczącą stosowania funkcji HWPARVT jest stosowanie jej tylko w przy- padku, gdy zostaniemy do tego zmuszeni. Oznacza to, że w przypadkach, w których konieczna będzie obecność deklaracji V[RGFGH, kompilacja programu zostanie wstrzy- mana. Wtedy będzie trzeba uzupełnić kod o wywołanie funkcji HWPARVT . W przypadku funkcji OGOAHWP i OGOAHWPATGH mamy zupełnie inną sytuację. Ich wywołanie jest konieczne przy każdym przekazywaniu metody do komponentu STL, ponieważ poza udostępnianiem potrzebnych deklaracji V[RGFGH dostosowują one składnię stosowaną przy wywoływaniu metod do składni stosowanej w całej bibliote- ce STL. Brak odpowiedniej funkcji przy przekazywaniu wskaźników na metody unie- możliwi poprawną kompilację programu. Pozostało nam omówić nazwy adaptorów metod. Okazuje się, że natkniemy się tutaj na historyczny już artefakt. Gdy okazało się, że potrzebne są takie adaptory, twórcy biblioteki STL skupili się na kontenerach wskaźników (W świetle ograniczeń, jakimi obarczone są te kontenery — opisano je w zagadnieniach 7., 20. i 33. — pewnym zaskoczeniem może być fakt, że to właśnie kontenery wskaźników obsługują klasy poli- morficzne, podczas gdy kontenery obiektów ich nie obsługują). Zbudowano adaptor dla metod i nazwano go OGOAHWP . Później okazało się, że potrzebny jest jeszcze adaptor Zagadnienie 42. Upewnij się, że less t () oznacza operator () 219 dla kontenerów obiektów, więc nową funkcję nazwano OGOAHWPATGH . Nie jest to zbyt eleganckie, ale takie rzeczy się zdarzają. Pewnie każdemu zdarzyło się nadać kompo- nentowi nazwę, którą później trudno było dostosować do nęowych warunków. Zagadnienie 42. Upewnij się, że less t () oznacza operator () Zagadnienie 42. Upewnij się, że less t () oznacza operator () Jak wszyscy dobrze wiemy, „widgety” mają swoją masę i makęsymalną prędkość: ENCUU9KFIGV] RWDNKE  UKGAVYGKIJV EQPUV UKGAVOCZ5RGGF EQPUV  _ Oczywiście naturalnym sposobem sortowania widgetów jest sortowanie ich według ma- sy, dlatego operator mniejszości () w tym przypadku powinien wyglądać następująco: DQQNQRGTCVQT EQPUV9KFIGVNJUEQPUV9KFIGVTJU ] TGVWTPNJUYGKIVJ TJUYGKIJV  _ Przypuśćmy jednak, że chcielibyśmy utworzyć kontener typu OWNVKUGV9KFIGV , w któ- rym widgety sortowane byłyby według ich prędkości maksymalnej. Wiemy już, że domyślną funkcją porównującą kontenera OWNVKUGV9KFIGV jest NGUU9KFIGV . Wiemy też, że domyślnie funkcja ta tylko wywołuje operator mniejszości (). Wyglą- da na to, że jedynym sposobem na posortowanie kontenera OWNVKUGV9KFIGV według prędkości maksymalnej jego elementów jest zniszczenie połączenia między funkcją NGUU9KFIGV a operatorem mniejszości (). Można to zrobić, nakazując funkcji NGUU9KFIGV kontrolę jedynie prędkości maksymalnej podawanych jeję widgetów: VGORNCVG VQLGUVURGELCNKCELCMNCU[ UVTWEVUVFNGUU9KFIGV UVFNGUU YKæCPCMNCUæ9KFIGV RWDNKERQCV[O LGUVVQHCVCNP[RQO[Uđ UVFDKPCT[AHWPEVKQP9KFIGV 9KFIGV DQQN ] DQQNQRGTCVQT EQPUV9KFIGVNJUEQPUV9KFIGVTJU EQPUV ] TGVWTPNJUOCZ5RGGF TJUOCZ5RGGF  _ _ Nie wygląda to na zbyt dobrą radę i taką nie jest, chyba jednak nie z powodu, o którym myślisz. Czyż nie jest zaskakujące, że ten kod w ogóle się kompiluje? Wielu programi- stów zauważy, że nie jest on tylko zwykłą specjalizacją szablonu, ale jest specjalizacją 220 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne szablonu w przestrzeni nazw UVF. Będą oni pytać: „Czy przestrzeń UVF nie powinna być święta? Dostępna tylko dla twórców biblioteki i będąca poza zasięgiem zwykłych programistów? Czy kompilatory nie powinny zakazywać grzebania w pracach twór- ców C++?”. Zazwyczaj próby modyfikacji komponentów w przestrzeni UVF są rzeczywiście za- bronione, a próby ich wykonania kończą się niezdefiniowanym zachowaniem aplika- cji. Jednak w niektórych przypadkach takie prowizorki są dopuszczalne. W szczegól- ności możliwe jest specjalizowanie szablonów do obsługi typów zdefiniowanych przez użytkownika. Niemal zawsze są inne, lepsze wyjścia niż zabawa z szablonami z prze- strzeni UVF, jednak czasami wiele argumentów przemawia właśnie za taką opcją. Na przykład autorzy klas inteligentnych wskaźników chcieliby, aby ich klasy zachowy- wały się jak zwyczajne wskaźniki, dlatego w takich klasach często spotyka się spe- cjalizacje funkcji UVFNGUU . Poniższy kod jest częścią klasy UJCTGFARVT z biblioteki Boost. Jest to właśnie inteligentny wskaźnik, o którym można przeczytać w podroz- działach „Zagadnienie 7.” i „Zagadnienie 50.”. PCOGURCEGUVF] VGORNCVGV[RGPCOG6 URGELCN KCELCHWPMELKUVFNGUU UVTWEVNGUUDQQUVUJCTGFARVT6  FNCMNCU[DQQUVUJCTGFARVT6 RWDNKE DQQU VVQRTGUVTGēPCY DKPCT[AHWPEVKQPDQQUVUJCTGFARVT6  DQQUVUJCTGFRVT6 VQLGUVV[RQYCMNCUC DQQN ]DCQY C QDCE CICFPKGPKG DQQNQRGTCVQT EQPUVDQQUVUJCTGFARVT6 C EQPUVDQQUVUJCTGFARVT6 D EQPUV ] TGVWTPNGUU6 CIGV DIGV UJCTGFARVTIGVYTCEC _Y [Mđ[YUMCļPKMMVÎT[LGUV QD KGMVGOUJCTGFARVT _ _ Powyższa implementacja nie jest pozbawiona sensu — z cała pewnością nie tworzy żadnych niespodzianek, ponieważ taka specjalizacja zapewnia jedynie, że sortowanie zwykłych wskaźników i wskaźników inteligentnych odbywa się w ten sam sposób. Niestety, nasza specjalizacja funkcji NGUU w klasie 9KFIGV może przysporzyć kilku niemiłych niespodzianek. Programistom C++ można wybaczyć, że pewne rzeczy uznają za oczywiste. Na przy- kład zakładają oni, że konstruktory kopiujące rzeczywiście kopiują obiekty (jak wy- kazano w podrozdziale „Zagadnienie 8.”, niedopełnienie tej konwencji może prowa- dzić do zadziwiających zachowań programu). Zakładają też, że pobierając adres obiektu, otrzymają wskaźnik na ten obiekt (w podrozdziale „Zagadnienie 18.” opisano problemy, jakie powstają, gdy nie jest to prawdą). Przyjmują za oczywiste, że adapto- ry takie jak DKPFUV i PQV można stosować z funktorami (podrozdział „Zagad- nienie 40.” opisuje problemy wynikające z niespełnienia tego założenia). Zakładają również, że operator dodawania ( ) dodaje (za wyjątkiem ciągów znaków, ale opera- tor ten jest już od dawna używany do łączenia ciągów), operator odejmowania () odejmuje, a operator porównania () porównuje obiekty. W końcu przyjmują za oczy- wiste, że zastosowanie funkcji NGUU jest równoznaczne z zastosowaniem operatora mniejszości (). Zagadnienie 42. Upewnij się, że less t () oznacza operator () 221 Operator mniejszości jest nie tylko domyślną implementacją funkcji NGUU , ale we- dług założeń programistów definiuje on sposób działania tej funkcji. Jeżeli funkcji NGUU nakażemy robić coś innego niż wywołanie operatora mniejszości (), pogwałci- my w ten sposób oczekiwania programistów. To całkowicie zaprzecza „zasadzie najmniejszego zaskoczenia” — takie działanie jest nieprzyzwoite, bezduszne i złe. Tak robić nie wolno. Nie wolno tego robić, szczególnie dlatego, że nie ma ku temu powodów. W bibliotece STL nie ma miejsca, w którym nie można by zastąpić funkcji NGUU innym rodzajem porównania. Wracając do naszego początkowego przykładu (czyli kontenera OWNVK UGV9KFIGV sortowanego według prędkości maksymalnej), aby osiągnąć zamierzony cel, musimy jedynie utworzyć klasę-funktor wykonującą potrzebne nam porównanie. Można ją nazwać prawie dowolnie, jednak na pewno nie można zastosować nazwy NGUU . Oto przykład takiej klasy: UVTWEV/CZ5RGGF QORCTG RWDNKEDKPCT[AHWPEVKQP9KFIGV9KFIGVDQQN ] DQQNQRGTCVQT EQPUV9KFIGVNJUEQPUV9KFIGVTJU EQPUV ] TGVWTPNJUOCZ5RGGF TJUOCZ5RGGF  _ _ Tworząc nasz kontener, jako funkcję porównującą wykorzystamy klasę /CZ5RGGF QO RCTG i w ten sposób unikniemy wykorzystania domyślnej funkcji porównującej, czyli NGUU9KFIGV . OWNVKUGV9KFIGV/CZ5RGGF QORCTG YKFIGVU Powyższy kod wykonuje dokładnie te operacje. Tworzy on kontener typu OWNVKUGV elementów 9KFIGV posortowanych zgodnie z definicją zawartą w klasie /CZ5RGGF QORCTG. Porównajmy to z kodem: OWNVKUGV9KFIGV YKFIGVU Tworzy on kontener typu OWNVKUGV elementów 9KFIGV posortowanych w sposób domyślny. Oznacza to, że do porównań wykorzystywana będzie funkcja NGUU9KFIGV , jednak każdy programista założy w tym miejscu, że odpowiadać za to będzie operator mniejszości (). Nie utrudniajmy życia innym, zmieniając domyślną definicję funkcji NGUU . Niech każde zastosowanie funkcji NGUU (bezpośrednie lub pośrednie) wiąże się z wykorzy- staniem operatora mniejszości. Jeżeli chcesz posortować obiekty za pomocą innego kryterium, zbuduj do tego specjalną klasę-funktor i nie nazywaj jej NGUU . To prze- cież takie proste.
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

STL w praktyce. 50 sposobów efektywnego wykorzystania
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: