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:
* 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.
STL w praktyce. 50 sposobów efektywnego wykorzystania
1. IDZ DO
PRZYK£ADOWY ROZDZIA£
SPIS TRE CI STL w praktyce. 50 sposobów
efektywnego wykorzystania
KATALOG KSI¥¯EK Autor: Scott Meyers
T³umaczenie: Adam Majczak (rozdz. 1-5),
KATALOG ONLINE Wojciech Moch (rozdz. 6, 7, dod. A-C)
ISBN: 83-7361-373-0
ZAMÓW DRUKOWANY KATALOG Tytu³ orygina³u: Effective STL 50 Specific Ways
to Improve Your Use of the Standard Template Library
Format: B5, stron: 282
TWÓJ KOSZYK
Standard Template Library to jedno z najpotê¿niejszych narzêdzi programistycznych
DODAJ DO KOSZYKA 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
CENNIK I INFORMACJE 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
ZAMÓW INFORMACJE powa¿niejszych aspektów STL pozwalaj¹cych zrozumieæ, dlaczego pewne rozwi¹zania
O NOWO CIACH 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
ZAMÓW CENNIK 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
CZYTELNIA w praktyce.
Ksi¹¿ka przedstawia:
FRAGMENTY KSI¥¯EK ONLINE
• 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.
Wydawnictwo Helion
ul. Chopina 6 „STL w praktyce. 50 sposobów efektywnego wykorzystania” to nieocenione ród³o
44-100 Gliwice wiedzy na temat jednego z najwa¿niejszych aspektów programowania w C++.
tel. (32)230-98-63 Je¿eli chcesz w praktyce wykorzystaæ STL, nie obêdziesz siê bez informacji
e-mail: helion@helion.pl zawartych w tej ksi¹¿ce.
2. Spis treści
Podziękowania................................................................................... 9
Przedmowa...................................................................................... 13
Wprowadzenie ................................................................................. 17
Rozdział 1. Kontenery........................................................................................ 29
Zagadnienie 1. Uwa nie dobierajmy kontenery................................................................30
Zagadnienie 2. Nie dajmy się zwieść iluzji o istnieniu kodów niezale nych
do zastosowanego kontenera........................................................................................35
Zagadnienie 3. Kopiowanie obiektów w kontenerach powinno być „tanie”,
łatwe i poprawne ..........................................................................................................40
Zagadnienie 4. Stosujmy metodę empty(), zamiast przyrównywać
rozmiar size() do zera...................................................................................................43
Zagadnienie 5. Preferujmy metody operujące na całych zakresach (podzbiorach),
bo są bardziej efektywne ni ich odpowiedniki operujące pojedynczymi elementami...45
Zagadnienie 6. Bądźmy wyczuleni i przygotowani na najbardziej
kłopotliwą interpretację kompilatora C++ ...................................................................56
Zagadnienie 7. Gdy stosujemy kontenery zawierające wskaźniki zainicjowane
za pomocą operatora new, pamiętajmy o zwolnieniu dynamicznej pamięci
operatorem delete, zanim zawierający je obiekt-kontener sam zostanie usunięty.......59
Zagadnienie 8. Nigdy nie twórzmy kontenerów zawierających wskaźniki
kategorii auto_ptr .........................................................................................................64
Zagadnienie 9. Uwa nie dobierajmy opcje do operacji kasowania ..................................67
Zagadnienie 10. Uwa ajmy na konwencje dynamicznej alokacji pamięci
i stosowne ograniczenia ...............................................................................................73
Zagadnienie 11. Zrozumienie uprawnionych zastosowań alokatorów pamięci
tworzonych przez u ytkownika....................................................................................80
Zagadnienie 12. Miejmy umiarkowane i realistyczne oczekiwania odnośnie
poziomu bezpieczeństwa wielu wątków obsługiwanych przez kontenery STL ..........84
3. 6 Spis treści
Rozdział 2. Kontenery typu vector oraz string..................................................... 89
Zagadnienie 13. Lepiej stosować kontenery vector oraz string ni dynamicznie
przydzielać pamięć tablicom........................................................................................90
Zagadnienie 14. Stosujmy metodę reserve, by uniknąć zbędnego przemieszczania
elementów w pamięci...................................................................................................93
Zagadnienie 15. Bądźmy ostro ni i wyczuleni na zró nicowanie implementacji
kontenerów typu string.................................................................................................96
Zagadnienie 16. Powinieneś wiedzieć, jak przesyłać dane z kontenerów vector
i string do klasycznego interfejsu zgodnego z C........................................................102
Zagadnienie 17. Sztuczka programistyczna „swap trick” pozwalająca
na obcięcie nadmiarowej pojemności ........................................................................106
Zagadnienie 18. Unikajmy stosowania wektora typu vector<bool>...............................108
Rozdział 3. Kontenery asocjacyjne ................................................................... 111
Zagadnienie 19. Ten sam czy taki sam — zrozumienie ró nicy pomiędzy
relacjami równości a równowa ności ........................................................................112
Zagadnienie 20. Określajmy typy porównawcze dla kontenerów asocjacyjnych
zawierających wskaźniki............................................................................................117
Zagadnienie 21. Funkcje porównujące powinny dla dwóch dokładnie równych
wartości zawsze zwracać wartość false......................................................................122
Zagadnienie 22. Unikajmy bezpośredniego modyfikowania klucza w kontenerach
typu set i multiset .......................................................................................................126
Zagadnienie 23. Rozwa my zastąpienie kontenerów asocjacyjnych
posortowanymi wektorami.........................................................................................133
Zagadnienie 24. Gdy efektywność działania jest szczególnie 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 ........................................................................................ 151
Zagadnienie 26. Lepiej wybrać iterator ni const_iterator, reverse_iterator
czy const_reverse_iterator..........................................................................................151
Zagadnienie 27. Stosujmy funkcje distance() i advance(), by przekształ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 istreambuf_iterator
do wczytywania danych znak po znaku .....................................................................163
Rozdział 5. Algorytmy...................................................................................... 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 algorytmami kategorii remove(),
jeśli naprawdę chcemy coś skutecznie usunąć z kontenera .......................................177
Zagadnienie 33. Przezornie i ostro nie stosujmy algorytmy kategorii remove()
wobec kontenerów zawierających wskaźniki ............................................................182
Zagadnienie 34. Zwracajmy uwagę, które z algorytmów oczekują
posortowanych zakresów ...........................................................................................186
4. Spis treści 7
Zagadnienie 35. Implementujmy zwykłe porównywanie łańcuchów znakó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_search(),
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 kompilatora związane
z biblioteką STL .........................................................................................................249
Zagadnienie 50. Poznaj strony WWW związane z biblioteką STL ................................256
Dodatek A Bibliografia .................................................................................... 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ędniania 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...................................................................................... 283
5. 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 funkcjami, 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.
6. 206 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne
XQKF SUQTV
XQKF
10. „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
(WPEVKQP YTCECPC RTG YCTVQ è
HQTAGCEJ
+PRWV+VGTCVQT HKTUV
+PRWV+VGTCVQT NCUV
(WPEVKQP H RTGMC[YCPC RTG YCTVQ è
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
RWDNKE WPCT[AHWPEVKQPKPV XQKF ] Y RQFTQFKCNG CICFPKGPKG
QRKUCPC
QUVCPKG MNCUC DCQYC
XQKF QRGTCVQT
KPV Z ] _
_
V[RGFGH FGSWGKPV KVGTCVQT GSWG+PV+VGT Y[IQFPC FGMNCTCELC V[RGFGH
FGSWGKPV FK
Q5QOGVJKPI F WVYQTGPKG HWPMVQTC
HQTAGCEJGSWG+PV+VGT Y[YQ CPKG HWPMELK HQTAGCEJ
Q5QOGVJKPI
FKDGIKP
RCTCOGVTCOK V[RW GSWG+PV+VGT
FKGPF
K Q5QOGVJKPI
F Y VGP URQUÎD F OWUK D[è RTGMCCPC
K YTÎEQPC RTG TGHGTGPELú
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
11. 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 obiektó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ącej du e ilości danych:
VGORNCVGV[RGPCOG 6
ENCUU $2(% $2(% $KI 2QN[OQTRJKE
RWDNKE (WPEVQT %NCUU
9KGNMC 2QNKHQTOKEPC
-NCUC(WPMVQT
WPCT[AHWPEVKQP6XQKF ] 6C MNCUC DCQYC QUVCPKG
QDLC PKQPC Y RQFTQFKCNG CICFPKGPKG
RTKXCVG
9KFIGV Y 6C MNCUC CYKGTC YKGNG FCP[EJ
KPV Z FNCVGIQ RTGMC[YCPKG KEJ RTG
YCTVQ è D[ QD[ PKGGHGMV[YPG
RWDNKE
XKTVWCN XQKF QRGTCVQT
EQPUV 6 XCN EQPUV VQ LGUV HWPMELC YKTVWCNPC
FNCVGIQ
TQE QPMQYCPKG NG PC PKæ
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[RGPCOG 6 MNCUC KORNGOGPVCELK FNC
ENCUU $2(%+ORN
RWDNKE WPCT[AHWPEVKQP6 XQKF ] OQF[HKMQYCPGL MNCU[ $2(%
RTKXCVG
9KFIGV Y RTGPKGUKQPQ VWVCL YU[UVMKG FCPG
KPV Z CYCTVG FQ VGL RQT[ Y MNCUKG $2(%
13. R+ORN VQ Uæ YU[UVMKG FCPG RTGEJQY[YCPG
RTG MNCUú $2(%
RWDNKE
XQKF QRGTCVQT
EQPUV 6 XCNEQPUV VC PKGYKTVWCNPC OGVQFC RTGMCWLG
] Y[YQ CPKG FQ MNCU[ $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 zdefiniowania pewnych pojęć.
14. 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 standardowych
kontenerach asocjacyjnych; wykorzystują je równie (pobierają 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 ycia 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 definicją 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ść logiczną. Jak mo na
się domyślić, w miejscach, w których STL oczekuje podania predykatu,
mo na podać albo rzeczywisty predykat albo obiekt-predykat.
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 $CF2TGFKECVG Y RQFTQFKCNG CICFPKGPKG
PCLFKGU
RWDNKE WPCT[AHWPEVKQP9KFIGV DQQN ] KPHQTOCELG PC VGOCV MNCU[ DCQYGL
RWDNKE
$CF2TGFKECVG
VKOGU%CNNGF
]_ KPKELCNKCELC OKGPPGL VKOGU%CNNGF
DQQN QRGTCVQT
EQPUV 9KFIGV
15. 210 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne
]
TGVWTP
VKOGU%CNNGF
_
RTKXCVG
UKGAV VKOGU%CNNGF
_
Przypuśćmy, e chcielibyśmy u yć tej klasy do usunięcia trzeciego elementu wektora
XGEVQT9KIFGV :
XGEVQT9KFIGV XY WVYÎT YGMVQT K WOKG è Y PKO
MKNMC GNGOGPVÎY
XYGTCUG
TGOQXGAKH
XYDGIKP
WUW VTGEK GNGOGPV
XYGPF
Y RQFTQFKCNG CICFPKGPKG
PCLFKGU
KPHQTOCELG FQV[EæEG
$CF2TGFKECVG
YKæMW HWPMELK GTCUG
K TGOQXGAKH
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+VGTCVQT V[RGPCOG 2TGFKECVG
(YF+VGTCVQT TGOQXGAKH
(YF+VGTCVQT DGIKP (YF+VGTCVQT GPF 2TGFKECVG R
]
DGIKP HKPFAKH
DGIKP GPF R
KH
DGIKP GPF TGVWTP DGIKP
GNUG ]
(YF+VGTCVQT PGZV DGIKP
TGVWTP TGOQXGAEQR[AKH
PGZV GPF DGIKP R
_
_
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 podrozdziale „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
16. 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:
ENCUU DCF2TGFKECVG
RWDNKE WPCT[AHWPEVKQP9KFIGV DQQN ]
RWDNKE
DQQN QRGTCVQT
EQPUV 9KFIGV EQPUV
]
TGVWTP
VKOGU%CNNGF D æF Y HWPMELCEJ V[RW EQPUV
_ PKG OQ PC OKGPKCè YCTVQ EK UM 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:
DQQN CPQVJGT$CF2TGFKECVG
EQPUV 9KFIGV EQPUV 9KFIGV
]
UVCVKE KPV VKOGU%CNNGF 0KG 0KG 0KG 0KG 0KG 0KG 0KG 0KG
TGVWTP
VKOGU%CNNGF 2TGF[MCV[ RQYKPP[ D[è HWPMELCOK E[UV[OK
_ C VCMKG HWPMELG PKG OCLæ UVCPW
Niezale nie od tego, w jaki sposób tworzysz swoje predykaty, powinny one zawsze być
funkcjami czystymi.
17. 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
18. i funkcję określającą, czy dany wskaźnik
identyfikuje interesujący nas element.
NKUV9KFIGV
20. RY
Je eli chcielibyśmy znaleźć pierwszy wskaźnik na interesujący nas element, mo na by
to zrobić w prosty sposób:
NKUV9KFIGV
21. KVGTCVQT K HKPFAKH
YKFIGV2VTUDGIKP
YKFIGV2VTUGPF
KU+PVGTGUVKPI
KH
K YKFIGV2VTUGPF
]
QRGTCELG YKæCPG RKGTYU[O
_ KPVGTGUWLæE[O PCU YUMC[YCP[O GNGOGPVGO
Je eli jednak będziemy chcieli uzyskać pierwszy wskaźnik na element nas nieinteresu-
jący, ten najbardziej oczywisty sposób nawet się nie skompiluje.
NKUV9KFIGV
22. KVGTCVQT K
HKPFAKH
YKFIGV2VTUDGIKP
YKFIGV2VTUGPF
PQV
KU+PVGTGUVKPI D æF PKG UMQORKNWLG UKú
W takim przypadku nale y funkcję KU+PVGTGUVKPI przekazać najpierw do funkcji
RVTAHWP
, a dopiero potem do adaptora PQV
.
NKUV9KFIGV
23. KVGTCVQT K
HKPFAKH
YKFIGV2VTUDGIKP
YKFIGV2VTUGPF
PQV
RVTAHWP
KU+PVGTGUVKPI VCM LGUV FQDTG
KH
K YKFIGV2VTUGPF
]
QRGTCELG YKæCPG RKGTYU[O
_ PKGKPVGTGUWLæE[O PCU YUMC[YCP[O GNGOGPVGO
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
24. 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[RGPCOG 6
ENCUU /GGVU6JTGUJQNF RWDNKE UVFWPCT[AHWPEVKQP9KFIGV DQQN ]
RTKXCVG
EQPUV 6 VJTGUJQNF
RWDNKE
/GGVU6JTGUJQNF
EQPUV 6 VJTGUJQNF
DQQN QRGTCVQT
EQPUV 9KFIGV EQPUV
_
UVTWEV 9KFIGV0COG%QORCTG
UVFDKPCT[AHWPEVKQP9KFIGV 9KFIGV DQQN ]
DQQN QRGTCVQT
EQPUV 9KFIGV NJU EQPUV 9KFIGV TJU 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
25. 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:
UVTWEV 9KFIGV0COG%QORCTG
RWDNKE UVFDKPCT[AHWPEVKQP9KFIGV 9KFIGV DQQN ]
DQQN QRGTCVQT
EQPUV 9KFIGV NJU EQPUV 9KFIGV TJU 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
30. 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 pisanie tego rodzaju rzeczy:
NKUV9KFIGV YKFIGVU
NKUV9KFIGV TGXGTUGAKVGTCVQT K PCLFWLG QUVCVPK GNGOGPV
HKPFAKH
YKFIGVUTDGIKP
YKFIGVUTGPF
MVÎT[ PKG RTGMTQE[
PQV
/GGVU6TGUJQNFKPV
RTQIW Q YCTVQ EK
EQMQNYKGM VQ PCE[
9KFIGV Y
CTIWOGPV[ MQPUVTWMVQTC
NKUV9KFIGV KVGTCVQT K PCLFWLG RKGTYU[ GNGOGPV
HKPFAKH
YKFIGVUDGIKP
YKFIGVUGPF
PCLFWLæE[ UKú RTGF Y
31. Zagadnienie 41. Po co stosować funkcje ptr_fun, mem_fun i mem_fun_ref? 215
DKPFPF
9KFIGV0COG%QORCTG
Y Y RQTæFMW UQTVQYCPKC
FGHKPKQYCP[O RTG
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 wycię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 CFPKC PT 5VQUQYCPC Y RT[RCFMW
IF[ HWPMELC H PKG LGUV OGVQFæ QDKGMVW Z
ZH
5M CFPKC PT 5VQUQYCPC Y RT[RCFMW
32. 216 Rozdział 6. ♦ Funktory, klasy-funktory, funkcje i inne
IF[ HWPMELC H LGUV OGVQFæ QDKGMVW
C Z LGUV QDKGMVGO NWD TGHGTGPELæ FQ
QDKGMVW
R H
5M CFPKC PT 5VQUQYCPC Y RT[RCFMW
IF[ HWPMELC H LGUV OGVQFæ QDKGMVW
C R LGUV YUMC PKMKGO PC QDKGMV Z
Teraz załó my, e mamy funkcję sprawdzającą elementy:
XQKF VGUV
9KFIGV Y URTCYFC Y K LG GNK PKG CNKE[ QP
VGUVW
QPCEC IQ LCMQ PKGY C EKY[
i kontener przechowujący te elementy:
XGEVQT9KFIGV XY XY RTGEJQYWLG GNGOGPV[
Je eli chcielibyśmy sprawdzić wszystkie elementy w XY, moglibyśmy w prosty sposób
wykorzystać funkcję HQTAGCEJ
:
HQTAGCEJ
XYDGIKP
XYGPF
VGUV 9[YQ CPKG PT
UMQORKNWLG UKú
Wyobraźmy sobie, e VGUV
nie jest zwyczajną funkcją, ale funkcją składową klasy
9KIFGV, co oznacza, e obiekty tej klasy mogą same się sprawdzać:
ENCUU 9KFIGV ]
RWDNKE
XQKF VGUV
Y[MQPWLG UCOQURTCYFGPKG LG GNK
QDKGMV
33. VJKU PKG RTGLFKG VGUVW
_ QPCECP[ LGUV LCMQ PKGY 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 CPKG PT
PKG UMQORKNWLG UKú
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
35. NRY NRY RTGEJQYWLG YUMC PKMK PC GNGOGPV[
HQTAGCEJ
NRYDGIKP
NRYGPF
9KFIGVVGUV 9[YQ CPKG PT
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.
36. 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+VGTCVQT V[RGPCOG (WPEVKQP
(WPEVKQP HQTAGCEJ
+PRWV+VGTCVQT DGIKP +PRWV+VGTCVQT GPF (WPEVKQP H
]
YJKNG
DGIKP GPF H
37. 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[RGPCOG 4 V[RGPCOG % FGMNCTCELC HWPMELK OGOAHWP FNC
PKGQPCEQP[EJ
OGOAHWPAV4 % LCMQ EQPUV HWPMELK UM CFQY[EJ
OGOAHWP
4
%
38. ROH
PKGRQDKGTCLæE[EJ CFP[EJ RCTCOGVTÎY
% VQ MNCUC C 4 VQ YCTVQ è YTCECPC
YUMC[YCPGL HWPMELK UM 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
39. NRY RQFQDPKG LCM Y[ GL
HQTAGCEJ
NRYDGIKP
NRYGPF
OGOAHWP
9KFIGVVGUV VGTC UKú UMQORKNWLG
funkcja HQTAGCEJ
otrzymuje obiekt typu OGOAHWPAV przechowujący wskaźnik na funk-
cję 9KFIGVVGUV. Dla ka dego wskaźnika 9KFIGV
40. 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
41. na składnię nr 1 stosowaną przez
funkcję HQTAGCEJ
, wobec czego nie powinno dziwić, e klasy w rodzaju OGOA HWPAV
nazywane są adaptorami obiektów funkcyjnych. W podobny sposób funkcja OGOAHWPA
TGH
przystosowuje składnię nr 2, generując obiekty-adaptory typu OGOAHWPATGHAV.
42. 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, dlaczego ten kod się skompiluje:
HQTAGCEJ
XYDGIKP
XYGPF
VGUV Y[YQ CPKG PT UMQORKNWLG UKú
a te nie:
HQTAGCEJ
XYDGIKP
XYGPF
9KFIGVVGUV Y[YQ CPKG PT PKG UMQORKNWLG
UKú
HQTAGCEJ
NRYDGIKP
NRYGPF
9KFIGVVGUV Y[YQ CPKG PT PKG UMQORKNWLG
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 UMQORKNWLG UKú K CFKC C
RQFQDPKG LCM Y[YQ CPKG PT
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
43. Zagadnienie 42. Upewnij się, że lesst() 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 nowych warunków.
Zagadnienie 42.
Upewnij się, że lesst()
oznacza operator()
Zagadnienie 42. Upewnij się, e lesst() oznacza operator()
Jak wszyscy dobrze wiemy, „widgety” mają swoją masę i maksymalną prędkość:
ENCUU 9KFIGV ]
RWDNKE
UKGAV YGKIJV
EQPUV
UKGAV OCZ5RGGF
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:
DQQN QRGTCVQT
EQPUV 9KFIGV NJU EQPUV 9KFIGV TJU
]
TGVWTP NJUYGKIVJ
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 VQ LGUV URGELCNKCELC MNCU[
UVTWEV UVFNGUU9KFIGV UVFNGUU YKæCPC MNCUæ 9KFIGV
RWDNKE RQC V[O LGUV VQ HCVCNP[ RQO[U
UVFDKPCT[AHWPEVKQP 9KFIGV
9KFIGV
DQQN ]
DQQN QRGTCVQT
EQPUV 9KFIGV NJU EQPUV 9KFIGV TJU EQPUV
]
TGVWTP NJUOCZ5RGGF
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ą
44. 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.”.
PCOGURCEG UVF ]
VGORNCVGV[RGPCOG 6 URGELCNKCELC HWPMELK UVFNGUU
UVTWEV NGUU DQQUVUJCTGFARVT6 FNC MNCU[ DQQUVUJCTGFARVT6
RWDNKE
DQQUV VQ RTGUVTG PCY
DKPCT[AHWPEVKQPDQQUVUJCTGFARVT6
DQQUVUJCTGF RVT6 VQ LGUV V[RQYC MNCUC
DQQN ] DCQYC
QDCE CICFPKGPKG
DQQN QRGTCVQT
EQPUV DQQUVUJCTGFARVT6 C
EQPUV DQQUVUJCTGFARVT6 D EQPUV
]
TGVWTP NGUU6
45. CIGV
DIGV
UJCTGFARVTIGV YTCEC
_ Y[M [ YUMC PKM MVÎT[ LGUV
QDKGMVGO UJCTGFARVT
_
_
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 ().
46. Zagadnienie 42. Upewnij się, że lesst() 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
RWDNKE DKPCT[AHWPEVKQP9KFIGV 9KFIGV DQQN ]
DQQN QRGTCVQT
EQPUV 9KFIGV NJU EQPUV 9KFIGV TJU EQPUV
]
TGVWTP NJUOCZ5RGGF
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.