1. IDZ DO PRZYKADOWY ROZDZIA C++. 50 efektywnych SPIS TRE CI
sposobw na udoskonalenie KATALOG KSIEK Twoich programw Autor: Scott
Meyers KATALOG ONLINE Tumaczenie: Mikoaj Szczepaniak ISBN:
83-7361-345-5 ZAMW DRUKOWANY KATALOG Tytu oryginau: Effective C++:
50 Specific Ways to Improve Your Programs and Design Format: B5,
stron: 248 TWJ KOSZYK Pierwsze wydanie ksiki C++. 50 efektywnych
sposobw na udoskonalenie twoich DODAJ DO KOSZYKA programw zostao
sprzedane w nakadzie 100 000 egzemplarzy i zostao przetumaczone na
cztery jzyki. Nietrudno zrozumie, dlaczego tak si stao. Scott
Meyers w charakterystyczny dla siebie, praktyczny sposb przedstawi
wiedz CENNIK I INFORMACJE typow dla ekspertw czynno ci, ktre niemal
zawsze wykonuj lub czynno ci, ktrych niemal zawsze unikaj, by
tworzy prosty, poprawny i efektywny kod. Kada z zawartych w tej
ksice pidziesiciu wskazwek jest streszczeniem metod ZAMW INFORMACJE
O NOWO CIACH pisania lepszych programw w C++, za odpowiednie
rozwaania s poparte konkretnymi przykadami. Z my l o nowym wydaniu,
Scott Meyers opracowa ZAMW CENNIK od pocztku wszystkie opisywane w
tej ksice wskazwki. Wynik jego pracy jest wyjtkowo zgodny z
midzynarodowym standardem C++, technologi aktualnych kompilatorw
oraz najnowszymi trendami w wiecie rzeczywistych aplikacji C++.
CZYTELNIA Do najwaniejszych zalet ksiki C++. 50 efektywnych sposobw
na udoskonalenie twoich programw nale: FRAGMENTY KSIEK ONLINE
Eksperckie porady dotyczce projektowania zorientowanego obiektowo,
projektowania klas i wa ciwego stosowania technik dziedziczenia
Analiza standardowej biblioteki C++, wcznie z wpywem standardowej
biblioteki szablonw oraz klas podobnych do string i vector na
struktur dobrze napisanych programw Rozwaania na temat najnowszych
moliwo ci jzyka C++: inicjalizacji staych wewntrz klas, przestrzeni
nazw oraz szablonw skadowych Wiedza bdca zwykle w posiadaniu
wycznie do wiadczonych programistw Ksika C++. 50 efektywnych
sposobw na udoskonalenie twoich programw pozostaje jedn z
najwaniejszych publikacji dla kadego programisty pracujcego z C++.
Scott Meyers jest znanym autorytetem w dziedzinie programowania w
jzyku C++; Wydawnictwo Helion zapewnia usugi doradcze dla klientw
na caym wiecie i jest czonkiem rady ul. Chopina 6 redakcyjnej pisma
C++ Report. Regularnie przemawia na technicznych konferencjach na
44-100 Gliwice caym wiecie, jest take autorem ksiek More Effective
C++ oraz Effective C++ CD. tel. (32)230-98-63 W 1993. roku otrzyma
tytu doktora informatyki na Brown University. e-mail:
[email protected]
2. Spis treci Przedmowa
...................................................................................................................
7 Podzikowania
............................................................................................................
11
Wstp.........................................................................................................................
15 Przejcie od jzyka C do
C++.......................................................................................
27 Sposb 1. Wybieraj const i inline zamiast
#define......................................................................28
Sposb 2. Wybieraj zamiast
.....................................................................31
Sposb 3. Wybieraj new i delete zamiast malloc i
free...............................................................33
Sposb 4. Stosuj komentarze w stylu C++
..................................................................................34
Zarzdzanie pamici
..................................................................................................
37 Sposb 5. U ywaj tych samych form w odpowiadajcych sobie
zastosowaniach operatorw new i delete
..............................................................................................38
Sposb 6. U ywaj delete w destruktorach dla skadowych wskanikowych
..............................39 Sposb 7. Przygotuj si do dziaania
w warunkach braku
pamici.............................................40 Sposb 8.
Podczas pisania operatorw new i delete trzymaj si istniejcej
konwencji ..............48 Sposb 9. Unikaj ukrywania normalnej
formy operatora new
................................................51 Sposb 10. Jeli
stworzye wasny operator new, opracuj tak e wasny operator delete
............53 Konstruktory, destruktory i operatory przypisania
......................................................... 61 Sposb
11. Deklaruj konstruktor kopiujcy i operator przypisania dla klas z
pamici przydzielan dynamicznie
........................................................................61
Sposb 12. Wykorzystuj konstruktory do inicjalizacji, a nie
przypisywania wartoci .................64 Sposb 13. Umieszczaj
skadowe na licie inicjalizacji w kolejnoci zgodnej z kolejnoci ich
deklaracji.........................................................................................69
Sposb 14. Umieszczaj w klasach bazowych wirtualne destruktory
............................................71 Sposb 15. Funkcja
operator= powinna zwraca referencj do *this
...........................................76 Sposb 16. Wykorzystuj
operator= do przypisywania wartoci do wszystkich skadowych
klasy......79 Sposb 17. Sprawdzaj w operatorze przypisania, czy nie
przypisujesz wartoci samej sobie......82 Klasy i funkcje projekt i
deklaracja..........................................................................
87 Sposb 18. Staraj si d y do kompletnych i minimalnych interfejsw
klas..............................89 Sposb 19. Rozr niaj funkcje
skadowe klasy, funkcje niebdce skadowymi klasy i funkcje
zaprzyjanione....................................................................................93
3. 6 Spis treci Sposb 20. Unikaj deklarowania w interfejsie
publicznym skadowych reprezentujcych dane ........98 Sposb 21.
Wykorzystuj stae wszdzie tam, gdzie jest to mo
liwe...........................................100 Sposb 22. Stosuj
przekazywanie obiektw przez referencje, a nie przez wartoci
...................106 Sposb 23. Nie prbuj zwraca referencji, kiedy
musisz zwrci obiekt ...................................109 Sposb
24. Wybieraj ostro nie pomidzy przeci aniem funkcji a domylnymi
wartociami parametrw
...................................................................113
Sposb 25. Unikaj przeci ania funkcji dla wskanikw i typw
numerycznych......................117 Sposb 26. Strze si
niejednoznacznoci...................................................................................120
Sposb 27. Jawnie zabraniaj wykorzystywania niejawnie generowanych
funkcji skadowych, ktrych stosowanie jest niezgodne z Twoimi zao
eniami..................123 Sposb 28. Dziel globaln przestrze nazw
................................................................................124
Implementacja klas i funkcji
......................................................................................
131 Sposb 29. Unikaj zwracania uchwytw do wewntrznych danych
.......................................132 Sposb 30. Unikaj funkcji
skadowych zwracajcych zmienne wskaniki lub referencje do skadowych,
ktre s mniej dostpne od tych funkcji ..................136 Sposb
31. Nigdy nie zwracaj referencji do obiektu lokalnego ani do
wskanika zainicjalizowanego za pomoc operatora new wewntrz tej
samej funkcji .............139 Sposb 32. Odkadaj definicje
zmiennych tak dugo, jak to tylko mo liwe
...............................142 Sposb 33. Rozwa nie stosuj
atrybut inline
................................................................................144
Sposb 34. Ograniczaj do minimum zale noci czasu kompilacji midzy
plikami....................150 Dziedziczenie i projektowanie
zorientowane obiektowo
............................................... 159 Sposb 35.
Dopilnuj, by publiczne dziedziczenie modelowao relacj jest
............................160 Sposb 36. Odr niaj dziedziczenie
interfejsu od dziedziczenia implementacji
........................166 Sposb 37. Nigdy nie definiuj ponownie
dziedziczonych funkcji niewirtualnych .....................174
Sposb 38. Nigdy nie definiuj ponownie dziedziczonej domylnej
wartoci parametru ............176 Sposb 39. Unikaj rzutowania w d
hierarchii
dziedziczenia....................................................178
Sposb 40. Modelujc relacje posiadania (ma) i implementacji z
wykorzystaniem, stosuj podzia na warstwy
.........................................................................................186
Sposb 41. Rozr niaj dziedziczenie od stosowania szablonw
................................................189 Sposb 42.
Dziedziczenie prywatne stosuj ostro nie
..................................................................193
Sposb 43. Dziedziczenie wielobazowe stosuj ostro
nie............................................................199
Sposb 44. Mw to, o co czym naprawd mylisz. Zdawaj sobie spraw z
tego, co mwisz ....213 Rozmaitoci
..............................................................................................................
215 Sposb 45. Miej wiadomo, ktre funkcje s niejawnie tworzone i
wywoywane przez C++....... 215 Sposb 46. Wykrywanie bdw kompilacji
i czenia jest lepsze od wykrywania bdw podczas wykonywania programw
....................................219 Sposb 47. Upewnij si, e
nielokalne obiekty statyczne s inicjalizowane przed ich u yciem
.......222 Sposb 48. Zwracaj uwag na ostrze enia
kompilatorw...........................................................226
Sposb 49. Zapoznaj si ze standardow bibliotek C++
...........................................................227
Sposb 50. Pracuj bez przerwy nad swoj znajomoci C++
.....................................................234 Skorowidz
.................................................................................................................
239
4. Dziedziczenie i projektowanie zorientowane obiektowo Wielu
programistw wyra a opini, e mo liwo dziedziczenia jest jedyn
korzyci pync z programowania zorientowanego obiektowo. Mo na mie
oczywicie r ne zdanie na ten temat, jednak liczba zawartych w
innych czciach tej ksi ki sposobw powiconych efektywnemu
programowaniu w C++ pokazuje, e masz do dyspozycji znacznie wicej
rozmaitych narzdzi, ni tylko okrelanie, ktre klasy powinny dzie-
dziczy po innych klasach. Projektowanie i implementowanie
hierarchii klas r ni si od zasadniczo od wszyst- kich mechanizmw
dostpnych w jzyku C. Problem dziedziczenia i projektowania
zorientowanego obiektowo z pewnoci zmusza do ponownego przemylenia
swojej strategii konstruowania systemw oprogramowania. Co wicej,
jzyk C++ udostpnia bardzo szeroki asortyment blokw budowania
obiektw, wcznie z publicznymi, chronionymi i prywatnymi klasami
bazowymi, wirtualnymi i niewirtualnymi klasami bazowymi oraz
wirtualnymi i niewirtualnymi funkcjami skadowymi. Ka da z wy-
mienionych wasnoci mo e wpywa tak e na pozostae komponenty jzyka
C++. W efekcie, prby zrozumienia, co poszczeglne wasnoci oznaczaj,
kiedy powinny by stosowane oraz jak mo na je w najlepszy sposb
poczy z nieobiektowymi cz- ciami jzyka C++ mo e niedowiadczonych
programistw zniechci. Dalsz komplikacj jest fakt, e r ne wasnoci
jzyka C++ s z pozoru odpowie- dzialne za te same zachowania. Oto
przykady: Potrzebujesz zbioru klas zawierajcych wiele elementw
wsplnych. Powiniene wykorzysta mechanizm dziedziczenia i stworzy
klasy potomne wzgldem jednej wsplnej klasy bazowej czy powiniene
wykorzysta szablony i wygenerowa wszystkie potrzebne klasy ze
wsplnym szkieletem kodu?
5. 160 Dziedziczenie i projektowanie zorientowane obiektowo
Klasa A ma zosta zaimplementowana w oparciu o klas B. Czy A powinna
zawiera skadow reprezentujc obiekt klasy B czy te powinna prywatnie
dziedziczy po klasie B? Potrzebujesz projektu bezpiecznej pod
wzgldem typu i homogenicznej klasy pojemnikowej, ktra nie jest
dostpna w standardowej bibliotece C++ (list pojemnikw udostpnianych
przez t bibliotek podano, prezentujc sposb 49.). Czy lepszym
rozwizaniem bdzie skonstruowanie szablonw czy budowa bezpiecznych
pod wzgldem typw interfejsw wok tej klasy, ktra sama byaby
zaimplementowana za pomoc oglnych (XQKF
6. ) wskanikw? W sposobach prezentowanych w tej czci zawarem
wskazwki, jak nale y znajdo- wa odpowiedzi na powy sze pytania. Nie
mog jednak liczy na to, e uda mi si znale waciwe rozwizania dla
wszystkich aspektw projektowania zorientowane- go obiektowo.
Zamiast tego skoncentrowaem si wic na wyjanianiu, co faktycznie
oznaczaj poszczeglne wasnoci jzyka C++ i co tak naprawd
sygnalizujesz, sto- sujc poszczeglne dyrektywy czy instrukcje.
Przykadowo, publiczne dziedziczenie oznacza relacj jest lub
specjalizacji-generalizacji (ang. isa, patrz sposb 35.) i jeli
musisz nada mu jakikolwiek inny sens, mo esz napotka pewne
problemy. Podobnie, funkcje wirtualne oznaczaj, e interfejs musi by
dziedziczony, natomiast funkcje niewirtualne oznaczaj, e
dziedziczony musi by zarwno interfejs, jak i imple- mentacja. Brak
rozr nienia tych znacze doprowadzi ju wielu programistw C++ do
trudnych do opisania nieszcz. Jeli rozumiesz znaczenia rozmaitych
wasnoci jzyka C++, odkryjesz, e Twj po- gld na projektowanie
zorientowane obiektowo powoli ewoluuje. Zamiast przekonywa Ci o
istniejcych r nicach pomidzy konstrukcjami jzykowymi, tre poni
szych sposobw uatwi Ci ocen jakoci opracowanych dotychczas systemw
oprogramo- wania. Bdziesz potem w stanie przeksztaci swoj wiedz w
swobodne i waciwe operowanie wasnociami jzyka C++ celem tworzenia
jeszcze lepszych programw. Wartoci wiedzy na temat znacze i
konsekwencji stosowania poszczeglnych kon- strukcji nie da si
przeceni. Poni sze sposoby zawieraj szczegow analiz metod
efektywnego stosowania omawianych wasnoci jzyka C++. W sposobie 44.
podsu- mowaem cechy i znaczenia poszczeglnych konstrukcji
obiektowych tego jzyka. Tre tego sposobu nale y traktowa jak
zwieczenie caej czci, a tak e zwize streszczenie, do ktrego warto
zaglda w przyszoci. Sposb 35. Dopilnuj, by publiczne dziedziczenie
modelowao relacj jest Sposb 35. Dopilnuj, by publiczne
dziedziczenie modelowao relacj jest William Dement w swojej ksi ce
pt. Some Must Watch While Some Must Sleep (W. H. Freeman and
Company, 1974) opisa swoje dowiadczenia z pracy ze studen- tami,
kiedy prbowa utrwali w ich umysach najistotniejsze tezy swojego
wykadu.
7. Sposb 35. Dopilnuj, by publiczne dziedziczenie modelowao
relacj jest 161 Mwi im, e przyjmuje si, e wiadomo historyczna
przecitnego brytyjskiego dziecka w wieku szkolnym wykracza poza
wiedz, e bitwa pod Hastings odbya si w roku 1066. William Dement
podkrela, e jeli dziecko pamita wicej szczegw, musi tak e pamita o
tej historycznej dla Brytyjczykw dacie. Na tej podstawie autor
wnioskuje, e w umysach jego studentw zachowuje si tylko kilka
istotnych i naj- ciekawszych faktw, wcznie z tym, e np. tabletki
nasenne powoduj bezsenno. Namawia studentw, by zapamitali
przynajmniej tych kilka najwa niejszych faktw, nawet jeli maj
zapomnie wszystkie pozostae zagadnienia dyskutowane podczas wykadw.
Autor ksi ki przekonywa do tego swoich studentw wielo- krotnie w
czasie semestru. Ostatnie pytanie testu w sesji egzaminacyjnej
brzmiao: wymie jeden fakt, ktry wy- niose z moich wykadw, i ktry na
pewno zapamitasz do koca ycia. Po spraw- dzeniu egzaminw Dement by
zszokowany niemal wszyscy napisali 1066. Jestem teraz peen obaw, e
jedynym istotnym wnioskiem, ktry wyniesiesz z tej ksi ki na temat
programowania zorientowanego obiektowo w C++ bdzie to, e me-
chanizm publicznego dziedziczenia oznacza relacj jest. Zachowaj
jednak ten fakt w swojej pamici. Jeli piszesz klas D (od ang.
Derived, czyli klas potomn), ktra publicznie dziedzi- czy po klasie
B (od ang. Base, czyli klasy bazowej), sygnalizujesz kompilatorom
C++ (i przyszym czytelnikom Twojego kodu), e ka dy obiekt typu D
jest tak e obiektem typu B, ale nie odwrotnie. Sygnalizujesz, e B
reprezentuje bardziej oglne pojcia ni D, natomiast D reprezentuje
bardziej konkretne pojcia ni B. Utrzymujesz, e wszdzie tam, gdzie
mo e by u yty obiekt typu B, mo e by tak e wykorzystany obiekt typu
D, poniewa ka dy obiekt typu D jest tak e obiektem typu B. Z
drugiej strony, jeli potrzebujesz obiektu typu D, obiekt typu B nie
bdzie mg go zastpi publiczne dziedziczenie oznacza relacj D jest B,
ale nie odwrotnie. Tak interpretacj publicznego dziedziczenia
wymusza jzyk C++. Przeanalizujmy poni szy przykad: ENCUU 2GTUQP ] _
ENCUU 5VWFGPV RWDNKE 2GTUQP ] _ Oczywiste jest, e ka dy student
jest osob, nie ka da osoba jest jednak studentem. Dokadnie takie
samo znaczenie ma powy sza hierarchia. Oczekujemy, e wszystkie
istniejce cechy danej osoby (np. to, e ma jak dat urodzenia)
istniej tak e dla stu- denta; nie oczekujemy jednak, e wszystkie
dane dotyczce studenta (np. adres szkoy, do ktrej uczszcza) bd
istotne dla wszystkich ludzi. Pojcie osoby jest bowiem bardziej
oglne, ni pojcie studenta student jest specyficznym rodzajem osoby.
W jzyku C++ ka da funkcja oczekujca argumentu typu 2GTUQP (lub
wskanika do obiektu klasy 2GTUQP bd referencji do obiektu klasy
2GTUQP) mo e zamiennie pobie- ra obiekt klasy 5VWFGPV (lub wskanik
do obiektu klasy 5VWFGPV bd referencj do obiektu klasy 5VWFGPV):
XQKF FCPEG EQPUV 2GTUQP R MC F[ OQ G VC E[ XQKF UVWF[ EQPUV 5VWFGPV
U V[NMQ UVWFGPEK OQI UVWFKQYC
8. 162 Dziedziczenie i projektowanie zorientowane obiektowo
2GTUQP R R TGRTGGPVWLG QUQD QDKGMV MNCU[ 2GTUQP 5VWFGPV U U
TGRTGGPVWLG UVWFGPVC QDKGMV MNCU[ 5VWFGPV FCPEG R FQDTG R
TGRTGGPVWLG QUQD FCPEG U FQDTG U TGRTGGPVWLG UVWFGPVC C YKE VCM G
QUQD UVWF[ U FQDTG UVWF[ R D F R PKG TGRTGGPVWLG UVWFGPVC Powy sze
komentarze s prawdziwe tylko dla publicznego dziedziczenia. C++
bdzie si zachowywa w opisany sposb tylko w przypadku, gdy klasa
5VWFGPV bdzie publicz- nie dziedziczya po klasie 2GTUQP.
Dziedziczenie prywatne oznacza co zupenie innego (patrz sposb 42.),
natomiast znaczenie dziedziczenia chronionego jest nieznane. Rwnowa
no dziedziczenia publicznego i relacji jest wydaje si oczywista, w
prakty- ce jednak waciwe modelowanie tej relacji nie jest ju takie
proste. Niekiedy nasza intuicja mo e si okaza zawodna. Przykadowo,
faktem jest, e pingwin to ptak; faktem jest tak e, e ptaki mog
lata. Gdybymy w swojej naiwnoci sprbowali wy- razi to w C++, nasze
wysiki przyniosyby efekt podobny do poni szego: ENCUU $KTF ] RWDNKE
XKTVWCN XQKF HN[ RVCMK OQI NCVC _ ENCUU 2GPIWKP RWDNKE $KTF ]
RKPIYKP[ U RVCMCOK _ Mamy teraz problem, poniewa z powy szej
hierarchii wynika, e pingwiny mog lata, co jest oczywicie nieprawd.
Co stao si z nasz strategi? W tym przypadku padlimy ofiar
nieprecyzyjnego jzyka naturalnego (polskiego). Kiedy mwimy, e ptaki
mog lata, w rzeczywistoci nie mamy na myli tego, e wszystkie ptaki
potrafi lata, a jedynie, e w oglnoci ptaki maj mo liwo latania.
Gdybymy byli bardziej precyzyjni, wyrazilibymy si inaczej, by
podkreli fakt, e istnieje wiele gatunkw ptakw, ktre nie lataj
otrzymalibymy wwczas poni - sz, znacznie lepiej modelujc
rzeczywisto, hierarchi klas: ENCUU $KTF ] DTCM FGMNCTCELK HWPMELK
HN[ _ ENCUU (N[KPI$KTF RWDNKE $KTF ] RWDNKE XKTVWCN XQKF HN[ _
ENCUU 0QP(N[KPI$KTF RWDNKE $KTF ] DTCM FGMNCTCELK HWPMELK HN[
_
9. Sposb 35. Dopilnuj, by publiczne dziedziczenie modelowao
relacj jest 163 ENCUU 2GPIWKP RWDNKE 0QP(N[KPI$KTF ] DTCM
FGMNCTCELK HWPMELK HN[ _ Powy sza hierarchia jest znacznie bli sza
naszej rzeczywistej wiedzy na temat pta- kw, ni ta zaprezentowana
wczeniej. Nasze rozwizanie nie jest jednak jeszcze skoczone,
poniewa w niektrych syste- mach oprogramowania, proste
stwierdzenie, e pingwin jest ptakiem, bdzie cakowicie poprawne. W
szczeglnoci, jeli nasza aplikacja dotyczy wycznie dziobw i skrzyde,
a w adnym stopniu nie wi e si z lataniem, oryginalna hierarchia
bdzie w zupeno- ci wystarczajca. Mimo e jest to dosy irytujce,
omawiana sytuacja jest prostym odzwierciedleniem faktu, e nie
istnieje jedna doskonaa metoda projektowania do- wolnego
oprogramowania. Dobry projekt musi po prostu uwzgldnia wymagania
stawiane przed tworzonym systemem, zarwno te w danej chwili
oczywiste, jak i te, ktre mog si pojawi w przyszoci. Jeli nasza
aplikacja nie musi i nigdy nie bdzie musiaa uwzgldnia mo liwoci
latania, rozwizaniem w zupenoci wystarczajcym bdzie stworzenie
klasy 2GPIWKP jako potomnej klasy $KTF. W rzeczywistoci taki
projekt mo e by nawet lepszy ni rozr nienie ptakw latajcych od
nielatajcych, poniewa takie rozr nienie mo e w ogle nie istnie w
modelowanym wiecie. Dodawanie do hierarchii niepotrzebnych klas
jest bdn decyzj projektow, ponie- wa narusza prawidowe relacje
dziedziczenia pomidzy klasami. Istnieje jeszcze inna strategia
postpowania w przypadku omawianego problemu: wszystkie ptaki mog
lata, pingwiny s ptakami, pingwiny nie mog lata. Strate- gia polega
na wprowadzeniu takich zmian w definicji funkcji HN[, by dla
pingwinw generowany by bd wykonania: XQKF GTTQT EQPUV UVTKPI OUI
HWPMELC FGHKPKQYCPC Y KPP[O OKGLUEW ENCUU 2GPIWKP RWDNKE $KTF ]
RWDNKE XKTVWCN XQKF HN[ ] GTTQT 2KPIYKP[ PKG OQI NCVC _ _ Do
takiego rozwizania d twrcy jzykw interpretowanych (jak Smalltalk),
jed- nak istotne jest prawidowe rozpoznanie rzeczywistego znaczenia
powy szego kodu, ktre jest zupenie inne, ni mgby przypuszcza. Ciao
funkcji nie oznacza bowiem, e pingwiny nie mog lata. Jej faktyczne
znaczenie to: pingwiny mog lata, jed- nak kiedy prbuj to robi,
powoduj bd. Na czym polega r nica pomidzy tymi znaczeniami? Wynika
przede wszystkim z mo liwoci wykrycia bdu ogranicze- nie pingwiny
nie mog lata mo e by egzekwowane przez kompilatory, natomiast
naruszenie ograniczenia podejmowana przez pingwiny prba latania
powoduje bd mo e zosta wykryte tylko podczas wykonywania programu.
Aby wyrazi ograniczenie pingwiny nie mog lata, wystarczy nie
definiowa odpowiedniej funkcji dla obiektw klasy 2GPIWKP: ENCUU
$KTF ] DTCM FGMNCTCELK HWPMELK HN[ _
10. 164 Dziedziczenie i projektowanie zorientowane obiektowo
ENCUU 0QP(N[KPI$KTF RWDNKE $KTF ] DTCM FGMNCTCELK HWPMELK HN[ _
ENCUU 2GPIWKP RWDNKE 0QP(N[KPI$KTF ] DTCM FGMNCTCELK HWPMELK HN[ _
Jeli sprbujesz teraz wywoa funkcj HN[ dla obiektu reprezentujcego
pingwina, kompilator zasygnalizuje bd: 2GPIWKP R RHN[ D F
Zaprezentowane rozwizanie jest cakowicie odmienne od podejcia
stosowanego w jzyku Smalltalk. Stosowana tam strategia powoduje, e
kompilator skompilowaby podobny kod bez przeszkd. Filozofia jzyka
C++ jest jednak zupenie inna ni filozofia jzyka Smalltalk, dopki
jednak programujesz w C++, powiniene stosowa si wycznie do regu
obowizu- jcych w tym jzyku. Co wicej, wykrywanie bdw w czasie
kompilacji (a nie w czasie wykonywania) programu wi e si z pewnymi
technicznymi korzyciami patrz sposb 46. By mo e przyznasz, e Twoja
wiedza z zakresu ornitologii ma raczej intuicyjny charakter i mo e
by zawodna, zawsze mo esz jednak polega na swojej biegoci w
dziedzinie podstawowej geometrii, prawda? Nie martw si, mam na myli
wycz- nie prostokty i kwadraty. Sprbuj wic odpowiedzie na pytanie:
czy reprezentujca kwadraty klasa 5SWCTG publicznie dziedziczy po
reprezentujcej prostokty klasie 4GEVCPING? Powiesz pewnie: Te co!
Ka de dziecko wie, e kwadrat jest prostoktem, ale w ogl- noci
prostokt nie musi by kwadratem. Tak, to prawda, przynajmniej na
poziomie gimnazjum. Nie sdz jednak, bymy kiedykolwiek wrcili do
nauki na tym poziomie. Przeanalizuj wic poni szy kod: ENCUU
4GEVCPING ] RWDNKE XKTVWCN XQKF UGV*GKIJV KPV PGY*GKIJV XKTVWCN
XQKF UGV9KFVJ KPV PGY9KFVJ XKTVWCN KPV JGKIJV EQPUV HWPMELG YTCECL
DKG EG XKTVWCN KPV YKFVJ EQPUV YCTVQ EK _
11. Sposb 35. Dopilnuj, by publiczne dziedziczenie modelowao
relacj jest 165 XQKF OCMG$KIIGT 4GEVCPING T HWPMELC YKMUCLEC RQNG ]
RQYKGTEJPK RTQUVQMVC T KPV QNF*GKIJV TJGKIJV TUGV9KFVJ TYKFVJ
FQFCLG FQ UGTQMQ EK T CUUGTV TJGKIJV QNF*GKIJV WRGYPKC UK G Y[UQMQ
_ RTQUVQMVC T PKG OKGPK C UK Jest oczywiste, e ostatnia instrukcja
nigdy nie zakoczy si niepowodzeniem, poniewa funkcja OCMG$KIIGT
modyfikuje wycznie szeroko prostokta reprezen- towanego przez T.
Rozwa teraz poni szy fragment kodu, w ktrym wykorzystujemy
publiczne dziedzi- czenie umo liwiajce traktowanie kwadratw jak
prostoktw: ENCUU 5SWCTG RWDNKE 4GEVCPING ] _ 5SWCTG U CUUGTV UYKFVJ
UJGKIJV YCTWPGM OWUK D[ RTCYFKY[ FNC YU[UVMKEJ MYCFTCVY OCMG$KIIGT
U PC UMWVGM FKGFKEGPKC U LGUV Y TGNCELK LGUV MNCU 4GEVCPING OQ GO[
YKE YKMU[ RQNG LGIQ RQYKGTEJPK CUUGTV U9KFVJ UJGKIJV YCTWPGM PCFCN
OWUK D[ RTCYFKY[ FNC YU[UVMKEJ MYCFTCVY Tak e teraz oczywiste jest,
e ostatni warunek nigdy nie powinien by faszywy. Zgodnie z
definicj, szeroko kwadratu jest przecie taka sama jak jego wysoko.
Tym razem mamy jednak problem. Jak mo na pogodzi poni sze
twierdzenia? Przed wywoaniem funkcji OCMG$KIIGT wysoko kwadratu
reprezentowanego przez obiekt U jest taka sama jak jego szeroko.
Wewntrz funkcji OCMG$KIIGT modyfikowana jest szeroko kwadratu,
jednak wysoko pozostaje niezmieniona. Po zakoczeniu wykonywania
funkcji OCMG$KIIGT wysoko kwadratu U ponownie jest taka sama jak
jego szeroko (zauwa , e obiekt U jest przekazywany do funkcji
OCMG$KIIGT przez referencj, zatem funkcja modyfikuje ten sam obiekt
U, nie jego kopi). Jak to mo liwe? Witaj w cudownym wiecie
publicznego dziedziczenia, w ktrym Twj instynkt sprawdzajcy si do
tej pory w innych dziedzinach, wcznie z matematyk mo e nie by tak
pomocny, jak tego oczekujesz. Zasadniczym problemem jest w tym
przy- padku to, e operacja, ktr mo na stosowa dla prostoktw (jego
szeroko mo e by zmieniana niezale nie od wysokoci), nie mo e by
stosowana dla kwadratw (definicja figury wymusza rwno jej szerokoci
i wysokoci). Mechanizm publiczne- go dziedziczenia zakada jednak, e
absolutnie wszystkie operacje stosowane z powo- dzeniem dla obiektw
klasy bazowej mog by stosowane tak e dla obiektw klasy
12. 166 Dziedziczenie i projektowanie zorientowane obiektowo
potomnej. W przypadku prostoktw i kwadratw (podobny przykad
dotyczcy zbio- rw i list omawiam w sposobie 40.) to zao enie si nie
sprawdza, zatem stosowanie publicznego dziedziczenia do modelowania
wystpujcej midzy nimi relacji jest po prostu bdne. Kompilatory
oczywicie umo liwi Ci zaprogramowanie takiego mo- delu, jednak jak
si ju przekonalimy nie mamy gwarancji, e nasz program bdzie si
zachowywa prawidowo. Od czasu do czasu ka dy programista musi si
przekona (niektrzy czciej, inni rzadziej), e poprawne skompilowanie
programu nie oznacza, e bdzie on dziaa zgodnie z oczekiwaniami. Nie
denerwuj si, e rozwijana przez lata intuicja dotyczca tworzonego
oprogramo- wania traci moc w konfrontacji z projektowaniem
zorientowanym obiektowo. Twoja wiedza jest nadal cenna, jednak
dodae wanie do swojego arsenau rozwiza pro- jektowych silny
mechanizm dziedziczenia i bdziesz musia rozszerzy swoj intuicj w
taki sposb, by prowadzia Ci do waciwego wykorzystywania nowych
umiejt- noci. Z czasem problem klasy 2GPIWKP dziedziczcej po klasie
$KTF lub klasie 5SWCTG dziedziczcej po klasie 4GEVCPING bdzie dla
Ciebie rwnie zabawny jak prezentowa- ne Ci przez niedowiadczonych
programistw funkcje zajmujce wiele stron. Mo li- we, e proponowane
podejcie do tego typu problemw jest waciwe, nadal jednak nie jest
to bardzo prawdopodobne. Relacja jest nie jest oczywicie jedyn
relacj wystpujc pomidzy klasami. Dwie pozostae powszechnie
stosowane relacje midzy klasami to relacja ma (ang. has-a) oraz
relacja implementacji z wykorzystaniem (ang.
is-implemented-in-terms-of). Relacje te przeanalizujemy podczas
prezentacji sposobw 40. i 42. Nierzadko pro- jekty C++ ulegaj
znieksztaceniom, poniewa ktra z pozostaych najwa niejszych relacji
zostaa bdnie zamodelowana jako jest, powinnimy wic by pewni, e
waciwie rozr niamy te relacje i wiemy, jak nale y je najlepiej
modelowa w C++. Sposb 36. Odrniaj dziedziczenie interfejsu od
dziedziczenia implementacji Sposb 36. Odr niaj dziedziczenie
interfejsu od dziedziczenia implementacji Po przeprowadzeniu
dokadnej analizy okazuje si, e pozornie oczywiste pojcie
(publicznego) dziedziczenia skada si w rzeczywistoci z dwch
rozdzielnych czci dziedziczenia interfejsw funkcji oraz
dziedziczenia implementacji funkcji. R nica pomidzy wspomnianymi
rodzajami dziedziczenia cile odpowiada r nicy pomi- dzy
deklaracjami a definicjami funkcji (omwionej we wstpie do tej ksi
ki). Jako projektant klasy potrzebujesz niekiedy takich klas
potomnych, ktre dziedzicz wycznie interfejs (deklaracj) danej
funkcji skadowej; czasem potrzebujesz klas potomnych dziedziczcych
zarwno interfejs, jak i implementacj danej funkcji, jednak masz
zamiar przykry implementacj swoim rozwizaniem; zdarza si tak e, e
potrzebujesz klas potomnych dziedziczcych zarwno interfejs, jak i
implementacj danej funkcji, ale bez mo liwoci przykrywania
czegokolwiek.
13. Sposb 36. Odrniaj dziedziczenie interfejsu od dziedziczenia
implementacji 167 Aby lepiej zrozumie r nic pomidzy zaproponowanymi
opcjami, przeanalizuj poni sz hierarchi klas reprezentujc figury
geometryczne w aplikacji graficznej: ENCUU 5JCRG ] RWDNKE XKTVWCN
XQKF FTCY EQPUV XKTVWCN XQKF GTTQT EQPUV UVTKPI OUI KPV
QDLGEV+& EQPUV _ ENCUU 4GEVCPING RWDNKE 5JCRG ] _ ENCUU 'NNKRUG
RWDNKE 5JCRG ] _ 5JCRG jest klas abstrakcyjn. Mo na to pozna po
obecnoci czystej funkcji wirtual- nej FTCY. W efekcie klienci nie
mog tworzy egzemplarzy klasy 5JCRG, mog to robi wycznie klasy
potomne. Mimo to klasa 5JCRG wywiera ogromny nacisk na wszyst- kie
klasy, ktre (publicznie) po niej dziedzicz, poniewa : Interfejsy
funkcji skadowych zawsze s dziedziczone. W sposobie 35. wyjaniem, e
dziedziczenie publiczne oznacza faktycznie relacj jest, zatem
wszystkie elementy istniejce w klasie bazowej musz tak e istnie w
klasach potomnych. Jeli wic dan funkcj mo na wykona dla danej
klasy, musi tak e istnie sposb jej wykonania dla jej podklas. W
funkcji 5JCRG zadeklarowalimy trzy funkcje. Pierwsza, FTCY, rysuje
na ekranie bie cy obiekt. Druga, GTTQT, jest wywoywana przez inne
funkcje skadowe w mo- mencie, gdy konieczne jest zasygnalizowanie
bdu. Trzecia, QDLGEV+&, zwraca unikalny cakowitoliczbowy
identyfikator bie cego obiektu (przykad wykorzystania tego typu
funkcji znajdziesz w sposobie 17.). Ka da z wymienionych funkcji
zostaa zadekla- rowana w inny sposb: FTCY jest czyst funkcj
wirtualn, GTTQT jest prost (nieczyst?) funkcj wirtualn, natomiast
QDLGEV+& jest funkcj niewirtualn. Jakie jest znaczenie tych
trzech r nych deklaracji? Rozwa my najpierw czyst funkcj wirtualn
FTCY. Dwie najistotniejsze cechy czys- tych funkcji wirtualnych to
konieczno ich ponownego zadeklarowania w ka dej dziedziczcej je
konkretnej klasie oraz brak ich definicji w klasach abstrakcyjnych.
Jeli poczymy te wasnoci, uwiadomimy sobie, e: Celem deklarowania
czystych funkcji wirtualnych jest otrzymanie klas potomnych
dziedziczcych wycznie interfejs. Jest to idealne rozwizanie dla
funkcji 5JCRGFTCY, poniewa naturalne jest udostp- nienie mo liwoci
rysowania wszystkich obiektw klasy 5JCRG, jednak niemo liwe jest
opracowanie jednej domylnej implementacji dla takiej funkcji.
Algorytm ryso- wania np. elips r ni si przecie znacznie od
algorytmu rysowania prostoktw. Waciwym sposobem interpretowania
znaczenia deklaracji funkcji 5JCRGFTCY jest instrukcja skierowana
do projektantw podklas: musicie stworzy funkcj FTCY, jed- nak nie
mam pojcia, jak moglibycie j zaimplementowa.
14. 168 Dziedziczenie i projektowanie zorientowane obiektowo
Istnieje niekiedy mo liwo opracowania definicji czystej funkcji
wirtualnej. Oznacza to, e mo esz stworzy tak implementacj dla
funkcji 5JCRGFTCY, e kompilatory C++ nie zgosz adnych zastrze e,
jednak jedynym sposobem jej wywoania byoby wykorzystanie penej
nazwy wcznie z nazw klasy: 5JCRG
15. RU PGY 5JCRG D F 5JCRG LGUV MNCU CDUVTCME[LP 5JCRG
17. RU PGY 'NNKRUG FQDTG RU FTCY Y[YQ WLG 'NNKRUGFTCY RU
5JCRGFTCY Y[YQ WLG 5JCRGFTCY RU 5JCRGFTCY Y[YQ WLG 5JCRGFTCY Poza
faktem, e powy sze rozwizanie mo e zrobi wra enie na innych
programi- stach podczas imprezy, w oglnoci znajomo zaprezentowanego
fenomenu jest w praktyce mao przydatna. Jak si jednak za chwil
przekonasz, mo e by wykorzy- stywana jako mechanizm udostpniania
bezpieczniejszej domylnej implementacji dla prostych (nieczystych)
funkcji wirtualnych. Niekiedy dobrym rozwizaniem jest
zadeklarowanie klasy zawierajcej wycznie czyste funkcje wirtualne.
Takie klasy protokou udostpniaj klasom potomnym jedy- nie
interfejsy funkcji, ale nigdy ich implementacje. Klasy protokou
opisaem, pre- zentujc sposb 34., i wspominam o nich ponownie w
sposobie 43. Znaczenie prostych funkcji wirtualnych jest nieco inne
ni znaczenie czystych funkcji wirtualnych. W obu przypadkach klasy
dziedzicz interfejsy funkcji, jednak proste funkcje wirtualne
zazwyczaj udostpniaj tak e swoje implementacje, ktre mog (ale nie
musz) by przykryte w klasach potomnych. Po chwili namysu powiniene
doj do wniosku, e: Celem deklarowania prostej funkcji wirtualnej
jest otrzymanie klas potomnych dziedziczcych zarwno interfejs, jak
i domyln implementacj tej funkcji. W przypadku funkcji 5JCRGGTTQT
interfejs okrela, e ka da klasa musi udostpnia funkcj wywoywan w
momencie wykrycia bdu, jednak obsuga samych bdw jest dowolna i zale
y wycznie od projektantw klas potomnych. Jeli nie przewiduj oni
adnych specjalnych dziaa w przypadku znalezienia bdu, mog
wykorzysta udostp- niany przez klas 5JCRG domylny mechanizm obsugi
bdw. Oznacza to, e rzeczywi- stym znaczeniem deklaracji funkcji
5JCRGGTTQT dla projektantw podklas jest zda- nie: musisz obsu y
funkcj GTTQT, jednak jeli nie chcesz tworzy wasnej wersji tej
funkcji, mo esz wykorzysta jej domyln wersj zdefiniowan dla klasy
5JCRG. Okazuje si, e zezwalanie prostym funkcjom wirtualnym na
precyzowanie zarwno deklaracji, jak i domylnej implementacji mo e
by niebezpieczne. Aby przekona si dlaczego, przeanalizuj
zaprezentowan poni ej hierarchi samolotw nale cych do linii
lotniczych XYZ. Linie XYZ posiadaj tylko dwa typy samolotw, Model A
i Model B, z ktrych oba lataj w identyczny sposb. Linie lotnicze
XYZ zaprojek- toway wic nastpujc hierarchi klas:
18. Sposb 36. Odrniaj dziedziczenie interfejsu od dziedziczenia
implementacji 169 ENCUU #KTRQTV ] _ TGRTGGPVWLG NQVPKUMC ENCUU
#KTRNCPG ] RWDNKE XKTVWCN XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP _ XQKF
#KTRNCPGHN[ EQPUV #KTRQTV FGUVKPCVKQP ] FQO[ NP[ MQF OQFGNWLE[
RTGNQV UCOQNQVW FQ FCPGIQ EGNW _ ENCUU /QFGN# RWDNKE #KTRNCPG ] _
ENCUU /QFGN$ RWDNKE #KTRNCPG ] _ Aby wyrazi fakt, e wszystkie
samoloty musz obsugiwa jak funkcj HN[ oraz z uwagi na mo liwe
wymagania dotyczce innych implementacji tej funkcji genero- wane
przez nowe modele samolotw, funkcja #KTRNCPGHN[ zostaa
zadeklarowana jako wirtualna. Aby unikn pisania identycznego kodu w
klasach /QFGN# i /QFGN$, domylny model latania zosta jednak
zapisany w formie ciaa funkcji #KTRNCPGHN[, ktre jest dziedziczone
zarwno przez klas /QFGN#, jak i klas /QFGN$. Jest to klasyczny
projekt zorientowany obiektowo. Dwie klasy wspdziel wsplny element
(sposb implementacji funkcji HN[), zatem element ten zostaje
przeniesiony do klasy bazowej i jest dziedziczony przez te dwie
klasy. Takie rozwizanie ma wiele istotnych zalet: pozwala unikn
powielania tego samego kodu, uatwia przysze roz- szerzenia systemu
i upraszcza konserwacj w dugim okresie czasu wszystkie wy- mienione
wasnoci s charakterystyczne wanie dla technologii obiektowej. Linie
lotnicze XYZ powinny wic by dumne ze swojego systemu. Przypumy
teraz, e firma XYZ rozwija si i postanowia pozyska nowy typ samo-
lotu Model C. Nowy samolot r ni si nieco od Modelu A i Modelu B, a
w szcze- glnoci ma inne waciwoci lotu. Programici omawianych linii
lotniczych dodaj wic do hierarchii klas repre- zentujc samoloty
Model C, jednak w popiechu zapomnieli ponownie zdefiniowa funkcj
HN[: ENCUU /QFGN% RWDNKE #KTRNCPG ] DTCM FGMNCTCELK HWPMELK HN[ _
Ich kod zawiera wic co podobnego do poni szego fragmentu: #KTRQTV
1MGEKG 1MEKG VQ NQVPKUMQ Y 9CTUCYKG #KTRNCPG
20. 170 Dziedziczenie i projektowanie zorientowane obiektowo
Mamy do czynienia z prawdziw katastrof, a mianowicie z prb obsu
enia lotu obiektu klasy /QFGN%, jakby by obiektem klasy /QFGN# lub
klasy /QFGN$. Z pewnoci nie wzbudzimy w ten sposb zaufania u
klientw linii lotniczych. Problem nie polega tutaj na tym, e
zdefiniowalimy domylne zachowanie funkcji #KTRNCPGHN[, tylko na
tym, e klasa /QFGN% moga przypadkowo (na skutek nieuwagi
programistw) dziedziczy to zachowanie. Na szczcie istnieje mo liwo
przekazywa- nia domylnych zachowa funkcji do podklas wycznie w
przypadku, gdy ich twrcy wyranie tego za daj. Sztuczka polega na
przerwaniu poczenia pomidzy interfejsem wirtualnej funkcji a jej
domyln implementacj. Oto sposb realizacji tego zadania: ENCUU
#KTRNCPG ] RWDNKE XKTVWCN XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP
RTQVGEVGF XQKF FGHCWNV(N[ EQPUV #KTRQTV FGUVKPCVKQP _ XQKF
#KTRNCPGFGHCWNV(N[ EQPUV #KTRQTV FGUVKPCVKQP ] FQO[ NP[ MQF
OQFGNWLE[ RTGNQV UCOQNQVW FQ FCPGIQ EGNW _ Zwr uwag na sposb, w
jaki przeksztacilimy #KTRNCPGHN[ w czyst funkcj wirtualn.
Zaprezentowana klasa udostpnia tym samym interfejs funkcji obsuguj-
cej latanie samolotw. Klasa #KTRNCPG zawiera tak e jej domyln
implementacj, jednak tym razem w formie niezale nej funkcji,
FGHCWNV(N[. Klasy podobne do /QFGN# i /QFGN$ mog wykorzysta domyln
implementacj, zwyczajnie wywoujc funkcj FGHCWNV(N[ wbudowan w ciao
ich funkcji HN[ (jednak zanim to zrobisz, prze- czytaj sposb 33.,
gdzie przeanalizowaem wzajemne oddziaywanie atrybutw KPNKPG i
XKTVWCN dla funkcji skadowych): ENCUU /QFGN# RWDNKE #KTRNCPG ]
RWDNKE XKTVWCN XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP ] FGHCWNV(N[
FGUVKPCVKQP _ _ ENCUU /QFGN$ RWDNKE #KTRNCPG ] RWDNKE XKTVWCN XQKF
HN[ EQPUV #KTRQTV FGUVKPCVKQP ] FGHCWNV(N[ FGUVKPCVKQP _ _ W
przypadku klasy /QFGN% nie mo emy ju przypadkowo dziedziczy
niepoprawnej implementacji funkcji HN[, poniewa czysta funkcja
wirtualna w klasie #KTRNCPG wy- musza na projektantach nowej klasy
stworzenie wasnej wersji funkcji HN[:
21. Sposb 36. Odrniaj dziedziczenie interfejsu od dziedziczenia
implementacji 171 ENCUU /QFGN% RWDNKE #KTRNCPG ] RWDNKE XKTVWCN
XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP _ XQKF /QFGN%HN[ EQPUV #KTRQTV
FGUVKPCVKQP ] MQF OQFGNWLE[ RTGNQV UCOQNQVW V[RW /QFGN% FQ FCPGIQ
EGNW _ Powy szy schemat postpowania nie jest oczywicie cakowicie
bezpieczny (progra- mici nadal maj mo liwo popeniania fatalnych w
skutkach bdw), jednak jest znacznie bardziej niezawodny od
oryginalnego projektu. Funkcja #KTRNCPGFGHCWNV(N[ jest chroniona,
poniewa w rzeczywistoci jest szczegem implementacyjnym klasy
#KTRNCPG i jej klas potomnych. Klienci wykorzystujcy te klasy
powinni zajmowa si wycznie wasnociami lotu reprezentowanych
samolotw, a nie sposobami imple- mentowania tych wasnoci. Wa ne
jest tak e to, e funkcja #KTRNCPGFGHCWNV(N[ jest niewirtualna.
Wynika to z faktu, e adna z podklas nie powinna jej ponownie
definiowa temu zagadnieniu powiciem sposb 37. Gdyby funkcja
FGHCWNV(N[ bya wirtualna, mielibymy do czynienia ze znanym nam ju
problemem: co stanie si, jeli projektant ktrej z pod- klas zapomni
ponownie zdefiniowa funkcj FGHCWNV(N[ w sytuacji, gdzie bdzie to
konieczne? Niektrzy programici sprzeciwiaj si idei definiowania
dwch osobnych funkcji dla interfejsu i domylnej implementacji (jak
HN[ i FGHCWNV(N[). Z jednej strony zauwa- aj, e takie rozwizanie
zamieca przestrze nazw klasy wystpujcymi wielokrotnie zbli onymi do
siebie nazwami funkcji. Z drugiej strony zgadzaj si z tez, e nale y
oddzieli interfejs od domylnej implementacji. Jak wic powinnimy
radzi sobie z t pozorn sprzecznoci? Wystarczy wykorzysta fakt, e
czyste funkcje wirtualne musz by ponownie deklarowane w podklasach,
ale mog tak e zawiera wasne implementacje. Oto sposb, w jaki mo emy
wykorzysta w hierarchii klas reprezen- tujcych samoloty mo liwo
definiowania czystej funkcji wirtualnej: ENCUU #KTRNCPG ] RWDNKE
XKTVWCN XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP _ XQKF #KTRNCPGHN[ EQPUV
#KTRQTV FGUVKPCVKQP ] FQO[ NP[ MQF OQFGNWLE[ RTGNQV UCOQNQVW FQ
FCPGIQ EGNW _ ENCUU /QFGN# RWDNKE #KTRNCPG ] RWDNKE XKTVWCN XQKF
HN[ EQPUV #KTRQTV FGUVKPCVKQP ] #KTRNCPGHN[ FGUVKPCVKQP _ _
22. 172 Dziedziczenie i projektowanie zorientowane obiektowo
ENCUU /QFGN$ RWDNKE #KTRNCPG ] RWDNKE XKTVWCN XQKF HN[ EQPUV
#KTRQTV FGUVKPCVKQP ] #KTRNCPGHN[ FGUVKPCVKQP _ _ ENCUU /QFGN%
RWDNKE #KTRNCPG ] RWDNKE XKTVWCN XQKF HN[ EQPUV #KTRQTV FGUVKPCVKQP
_ XQKF /QFGN%HN[ EQPUV #KTRQTV FGUVKPCVKQP ] MQF OQFGNWLE[ RTGNQV
UCOQNQVW V[RW /QFGN% FQ FCPGIQ EGNW _ Powy szy schemat niemal nie r
ni si od wczeniejszego projektu z wyjtkiem ciaa czystej funkcji
wirtualnej #KTRNCPGHN[, ktra zastpia wykorzystywan wczeniej niezale
n funkcj #KTRNCPGFGHCWNV(N[. W rzeczywistoci funkcja HN[ zostaa
roz- bita na dwa najwa niejsze elementy. Pierwszym z nich jest
deklaracja okrelajca jej interfejs (ktry musi by wykorzystywany
przez klasy potomne), natomiast drugim jest definicja okrelajca
domylne zachowanie funkcji (ktra mo e by wykorzystana w klasach
domylnych, ale tylko na wyrane danie ich projektantw). czc funk-
cje HN[ i FGHCWNV(N[, stracilimy jednak mo liwo nadawania im r nych
ogranicze dostpu kod, ktry wczeniej by chroniony (funkcja
FGHCWNV(N[ bya zadeklaro- wana w bloku RTQVGEVGF) bdzie teraz
publiczny (poniewa znajduje si w zadekla- rowanej w bloku RWDNKE
funkcji HN[). Wrmy do nale cej do klasy 5JCRG niewirtualnej funkcji
QDLGEV+&. Kiedy funkcja skadowa jest niewirtualna, w
zamierzeniu nie powinna zachowywa si w klasach potomnych inaczej ni
w klasie bazowej. W rzeczywistoci niewirtualne funkcje skadowe
opisuj zachowanie niezale ne od specjalizacji, poniewa
implementacja funkcji nie powinna ulega adnym zmianom, niezale nie
od specjalizacji kolejnych poziomw w hierarchii klas potomnych. Oto
pyncy z tego wniosek: Celem deklarowania niewirtualnej funkcji jest
otrzymanie klas potomnych dziedziczcych zarwno interfejs, jak i
wymagan implementacj tej funkcji. Mo esz pomyle, e deklaracja
funkcji 5JCRGQDLGEV+& oznacza: ka dy obiekt klasy 5JCRG zawiera
funkcj zwracajc identyfikator obiektu, ktry zawsze jest wyznaczany
w ten sam sposb (opisany w definicji funkcji 5JCRGQDLGEV+&),
ktrego adna klasa potomna nie powinna prbowa modyfikowa. Poniewa
niewirtualna funkcja opi- suje zachowanie niezale ne od
specjalizacji, nigdy nie powinna by ponownie dekla- rowana w adnej
podklasie (to zagadnienie szczegowo omwiem w sposobie 37.). R nice
pomidzy deklaracjami czystych funkcji wirtualnych, prostych funkcji
wirtu- alnych oraz funkcji niewirtualnych umo liwiaj dokadne
precyzowanie waciwych dla danego mechanizmw dziedziczenia tych
funkcji przez klasy potomne: dzie- dziczenia samego interfejsu,
dziedziczenia interfejsu i domylnej implementacji lub dziedziczenia
interfejsu i wymaganej implementacji. Poniewa wymienione r ne
23. Sposb 36. Odrniaj dziedziczenie interfejsu od dziedziczenia
implementacji 173 typy deklaracji oznaczaj zupenie inne mechanizmy
dziedziczenia, podczas de- klarowania funkcji skadowych musisz
bardzo ostro nie wybra jedn z omawia- nych metod. Pierwszym
popularnym bdem jest deklarowanie wszystkich funkcji jako
niewirtual- nych. Eliminujemy w ten sposb mo liwo specjalizacji
klas potomnych; szczegl- nie kopotliwe s w tym przypadku tak e
niewirtualne destruktory (patrz sposb 14.). Jest to oczywicie dobre
rozwizanie dla klas, ktre w zao eniu nie bd wykorzy- stywane w
charakterze klas bazowych. W takim przypadku, zastosowanie zbioru
wy- cznie niewirtualnych funkcji skadowych jest cakowicie poprawne.
Zbyt czsto jednak wynika to wycznie z braku wiedzy na temat r ni
pomidzy funkcjami wirtualnymi a niewirtualnymi lub nieuzasadnionych
obaw odnonie wydajnoci funkcji wirtualnych. Nale y wic pamita o
fakcie, e niemal wszystkie klasy, ktre w przy- szoci maj by
wykorzystane jako klasy bazowe, powinny zawiera funkcje wirtu- alne
(ponownie patrz sposb 14.). Jeli obawiasz si kosztw zwizanych z
funkcjami wirtualnymi, pozwl, e przypo- mn Ci o regule 80-20 (patrz
tak e sposb 33.), ktra mwi, e 80 procent czasu dziaania programu
jest powicona wykonywaniu 20 procent jego kodu. Wspomnia- na regua
jest istotna, poniewa oznacza, e rednio 80 procent naszych wywoa
funkcji mo e by wirtualnych i bdzie to miao niemal niezauwa alny
wpyw na ca- kowit wydajno naszego programu. Zanim wic zaczniesz si
martwi, czy mo esz sobie pozwoli na koszty zwizane z wykorzystaniem
funkcji wirtualnych, upewnij si, czy Twoje rozwa ania dotycz tych
20 procent programu, gdzie decyzja bdzie miaa istotny wpyw na
wydajno caego programu. Innym powszechnym problemem jest
deklarowanie wszystkich funkcji jako wirtualne. Niekiedy jest to
oczywicie waciwe rozwizanie np. w przypadku klas protokou (patrz
sposb 34.). Mo e jednak wiadczy tak e o zwykej niewiedzy
projektanta klasy. Niektre deklaracje funkcji nie powinny umo liwia
ponownego ich definio- wania w klasach potomnych w takich
przypadkach jedynym sposobem osignicia tego celu jest deklarowanie
tych funkcji jako niewirtualnych. Nie ma przecie naj- mniejszego
sensu udostpnianie innym programistom klas, ktre maj by dziedzi-
czone przez inne klasy i ktrych wszystkie funkcje skadowe bd
ponownie definio- wane. Pamitaj, e jeli masz klas bazow $, klas
potomn & oraz funkcj skadow OH, wwczas ka de z poni szych wywoa
funkcji OH musi by prawidowe: &
24. RF PGY & D
25. RD RF RD OH Y[YQ WLG HWPMEL OH C RQOQE YUMC PKMC FQ MNCU[
DCQYGL RF OH Y[YQ WLG HWPMEL OH C RQOQE YUMC PKMC FQ MNCU[ RQVQOPGL
Niekiedy musisz zadeklarowa funkcj OH jako niewirtualn, by upewni
si, e wszystko bdzie dziaao zgodnie z Twoimi oczekiwaniami (patrz
sposb 37.). Jeli dziaanie funkcji powinno by niezale ne od
specjalizacji, nie obawiaj si takiego rozwizania.
26. 174 Dziedziczenie i projektowanie zorientowane obiektowo
Sposb 37. Nigdy nie definiuj ponownie dziedziczonych funkcji
niewirtualnych Sposb 37. Nigdy nie definiuj ponownie dziedziczonych
funkcji niewirtualnych Istniej dwa podejcia do tego problemu:
teoretyczne i pragmatyczne. Zacznijmy od po- dejcia pragmatycznego
(teoretycy s w kocu przyzwyczajeni do cierpliwego czekania).
Przypumy, e powiem Ci, e klasa & publicznie dziedziczy po
klasie $ i istnieje publiczna funkcja skadowa OH zdefiniowana w
klasie $. Parametry i warto zwraca- ne przez funkcj OH s dla nas na
tym etapie nieistotne, za my wic, e maj posta XQKF. Innymi sowy, mo
emy to wyrazi w nastpujcy sposb: ENCUU $ ] RWDNKE XQKF OH _ ENCUU
& RWDNKE $ ] _ Nawet gdybymy nic nie wiedzieli o $, & i OH,
majc dany obiekt Z klasy &: & Z Z LGUV QDKGMVGO V[RW &
bylibymy bardzo zaskoczeni, gdyby instrukcje: $
27. R$ Z QVT[OWLG YUMC PKM FQ Z R$ OH Y[YQ WLG HWPMEL OH C
RQOQE YUMC PKMC powodoway inne dziaanie, ni instrukcje: &
28. R& Z QVT[OWLG YUMC PKM FQ Z R& OH Y[YQ WLG HWPMEL
OH C RQOQE YUMC PKMC Wynika to z faktu, e w obu przypadkach
wywoujemy funkcj skadow OH dla obiektu Z. Poniewa w obu przypadkach
jest to ta sama funkcja i ten sam obiekt, efekt wywoania powinien
by identyczny, prawda? Tak, powinien, ale nie jest. W szczeglnoci,
rezultaty wywoania bd inne, jeli OH bdzie funkcj niewirtualn, a
klasa & bdzie zawieraa definicj wasnej wersji tej funkcji:
ENCUU & RWDNKE $ ] RWDNKE XQKF OH WMT[YC FGHKPKEL $OH RCVT
URQUD _ R$ OH Y[YQ WLG HWPMEL $OH R& OH Y[YQ WLG HWPMEL
&OH
29. Sposb 37. Nigdy nie definiuj ponownie dziedziczonych
funkcji niewirtualnych 175 Powodem takiego dwulicowego zachowania
jest fakt, e niewirtualne funkcje $OH i &OH s wizane statycznie
(patrz sposb 38.). Oznacza to, e poniewa zmienna R$ zostaa
zadeklarowana jako wskanik do $, niewirtualne funkcje wywoywane za
po- rednictwem tej zmiennej zawsze bd tymi zdefiniowanymi dla klasy
$, nawet jeli R$ wskazuje na obiekt klasy pochodnej wzgldem $ (jak
w powy szym przykadzie). Z drugiej strony, funkcje wirtualne s
wizane dynamicznie (ponownie patrz sposb 38.), co oznacza, e
opisywany problem ich nie dotyczy. Gdyby OH bya funkcj wirtualn,
jej wywoanie (niezale nie od tego, czy z wykorzystaniem wskanika do
R$ czy do R&) spowodowaoby wywoanie wersji &OH, poniewa R$
i R& w rzeczywistoci wskazuj na obiekt klasy &. Nale y
pamita, e jeli tworzymy klas & i ponownie definiujemy
dziedziczon po klasie $ niewirtualn funkcj OH, obiekty klasy &
bd si prawdopodobnie okazyway zachowania godne schizofrenika. W
szczeglnoci dowolny obiekt klasy & mo e w odpowiedzi na
wywoanie funkcji OH zachowywa si albo jak obiekt klasy $, albo jak
obiekt klasy &; czynnikiem rozstrzygajcym nie bdzie tutaj sam
obiekt, ale zadeklarowany typ wskazujcego na ten obiekt wskanika.
Rwnie zdumiewajce za- chowanie zaprezentowayby w takim przypadku
referencje do obiektw. To ju wszystkie argumenty wysuwane przez
praktykw. Chcesz pewnie teraz pozna jakie teoretyczne uzasadnienie,
dlaczego nie nale y ponownie definiowa dziedzi- czonych funkcji
niewirtualnych. Wyjani to z przyjemnoci. W sposobie 35. pokazaem, e
publiczne dziedziczenie oznacza w rzeczywistoci relacj jest; w
sposobie 36. opisaem, dlaczego deklarowanie niewirtualnych funk-
cji w klasie powoduje niezale no od ewentualnych specjalizacji tej
klasy. Jeli wa- ciwie wykorzystasz wnioski wyniesione z tych
sposobw podczas projektowania klas $ i & oraz podczas tworzenia
niewirtualnej funkcji skadowej $OH, wwczas: Wszystkie funkcje, ktre
mo na stosowa dla obiektw klasy $, mo na stosowa tak e dla obiektw
klasy &, poniewa ka dy obiekt klasy & jest obiektem klasy
$. Podklasy klasy $ musz dziedziczy zarwno interfejs, jak i
implementacj funkcji OH, poniewa funkcja ta zostaa zadeklarowana w
klasie $ jako niewirtualna. Jeli w klasie & ponownie
zdefiniujemy teraz funkcj OH, w naszym projekcie powsta- nie
sprzeczno. Jeli klasa & faktycznie potrzebuje wasnej
implementacji funkcji OH, ktra bdzie si r nia od implementacji
dziedziczonej po klasie $, oraz jeli ka dy obiekt klasy $ (niezale
nie od poziomu specjalizacji) rzeczywicie musi wykorzysty- wa
implementacji tej funkcji z klasy $, wwczas stwierdzenie, e &
jest $ jest zwy- czajnie nieprawdziwe. Klasa & nie powinna w
takim przypadku publicznie dziedzi- czy po klasie $. Z drugiej
strony, jeli & naprawd musi publicznie dziedziczy po $ oraz
jeli & naprawd musi implementowa funkcj OH inaczej, ni
implementuje j klasa $, wwczas nieprawd jest, e OH odzwierciedla
niezale no od specjalizacji klasy $. W takim przypadku funkcja OH
powinna zosta zadeklarowana jako wirtualna. Wreszcie, jeli ka dy
obiekt klasy & naprawd musi by w relacji jest z obiektem klasy
$ oraz jeli funkcja OH rzeczywicie reprezentuje niezale no od
specjalizacji klasy $, wwczas klasa & nie powinna potrzebowa
wasnej implementacji funkcji OH i jej projektant nie powinien wic
podejmowa podobnych prb.
30. 176 Dziedziczenie i projektowanie zorientowane obiektowo
Niezale nie od tego, ktry argument najbardziej pasuje do naszej
sytuacji, oczywiste jest, e ponowne definiowanie dziedziczonych
funkcji niewirtualnych jest cakowicie pozbawione sensu. Sposb 38.
Nigdy nie definiuj ponownie dziedziczonej domylnej wartoci
parametru Sposb 38. Nigdy nie definiuj ponownie dziedziczonej
domylnej wartoci parametru Sprbujmy uproci nasze rozwa ania od
samego pocztku. Domylny parametr mo e istnie wycznie jako cz
funkcji, a nasze klasy mog dziedziczy tylko dwa ro- dzaje funkcji
wirtualne i niewirtualne. Jedynym sposobem ponownego zdefinio-
wania wartoci domylnej parametru jest wic ponowne zdefiniowanie
caej dziedzi- czonej funkcji. Ponowne definiowanie dziedziczonej
niewirtualnej funkcji jest jednak zawsze bdne (patrz sposb 37.), mo
emy wic od razu ograniczy nasz analiz do sytuacji, w ktrej
dziedziczymy funkcj wirtualn z domyln wartoci parametru. W takim
przypadku wyjanienie sensu umieszczania tego sposobu w ksi ce jest
bar- dzo proste funkcje wirtualne s wizane dynamicznie, ale domylne
wartoci ich parametrw s wizane statycznie. Co to oznacza? By mo e
nie posugujesz si biegle najnowszym argonem zwiza- nym z
programowaniem obiektowym lub zwyczajnie zapomniae, jakie s r nice
pomidzy wizaniem statycznym a wizaniem dynamicznym. Przypomnijmy
wic sobie, o co tak naprawd chodzi. Typem statycznym obiektu jest
ten typ, ktry wykorzystujemy w deklaracji obiektu w kodzie
programu. Przeanalizujmy poni sz hierarchi klas: ENCUU 5JCRG%QNQT ]
4'& )4''0 $.7' _ MNCUC TGRTGGPVWLEC HKIWT[ IGQOGVT[EPG ENCUU
5JCRG ] RWDNKE YU[UVMKG HKIWT[ OWU WFQUVRPKC T[UWLEG LG HWPMELG
XKTVWCN XQKF FTCY 5JCRG%QNQT EQNQT 4'& EQPUV _ ENCUU 4GEVCPING
RWDNKE 5JCRG ] RWDNKE YT WYCI PC KPPC FQO[ NP YCTVQ RCTCOGVTW NG
XKTVWCN XQKF FTCY 5JCRG%QNQT EQNQT )4''0 EQPUV _ ENCUU %KTENG
RWDNKE 5JCRG ] RWDNKE XKTVWCN XQKF FTCY 5JCRG%QNQT EQNQT EQPUV
_
31. Sposb 38. Nigdy nie definiuj ponownie dziedziczonej
domylnej wartoci parametru 177 Powy sz hierarchi mo na przedstawi
graficznie: Rozwa my teraz poni sze wskaniki: 5JCRG
32. RU UVCV[EP[ V[R 5JCRG
33. 5JCRG
34. RE PGY %KTENG UVCV[EP[ V[R 5JCRG
35. 5JCRG
36. RT PGY 4GEVCPING UVCV[EP[ V[R 5JCRG
37. W powy szym przykadzie RU, RE i RT s zadeklarowane jako
zmienne typu wskani- kowego do obiektw klasy 5JCRG, zatem wszystkie
nale do typu statycznego. Zauwa , e nie ma w tym przypadku
znaczenia, na co wymienione zmienne faktycz- nie wskazuj ich
statycznym typem jest 5JCRG
38. . Typ dynamiczny obiektu zale y od typu obiektu aktualnie
przez niego wskazywanego. Oznacza to, e od dynamicznego typu zale y
zachowanie obiektu. W powy szym przykadzie typem dynamicznym
zmiennej RE jest %KTENG
39. , za typem dynamicznym zmiennej RT jest 4GEVCPING
40. . Inaczej jest w przypadku zmiennej RU, ktra nie ma
dynamicznego typu, poniewa w rzeczywistoci nie wskazuje na aden
obiekt. Typy dynamiczne (jak sama nazwa wskazuje) mog si zmienia w
czasie wykony- wania programu, tego rodzaju zmiany odbywaj si
zazwyczaj na skutek wykonania operacji przypisania: RU RE V[RGO
F[PCOKEP[O YUMC PKMC RU LGUV VGTC %KTENG
42. Wirtualne funkcje s wizane dynamicznie, co oznacza, e
konkretna wywoywana funkcja zale y od dynamicznego typu obiektu
wykorzystywanego do jej wywoania: RE FTCY 4'& Y[YQ WLG HWPMEL
%KTENGFTCY 4'& RT FTCY 4'& Y[YQ WLG HWPMEL 4GEVCPINGFTCY
4'& Uwa asz pewnie, e nie ma powodw wraca do tego tematu z
pewnoci rozu- miesz ju znaczenie funkcji wirtualnych. Problemy
ujawniaj si dopiero w momencie, gdy analizujemy funkcje wirtualne z
domylnymi wartociami parametrw, poniewa jak ju wspomniaem funkcje
wirtualne s wizane dynamicznie, a domylne parametry C++ wi e
statycznie. Oznacza to, e mo esz wywoa wirtualn funkcj zdefiniowan
w klasie potomnej, ale z domyln wartoci parametru z klasy bazowej:
RT FTCY Y[YQ WLG 4GEVCPINGFTCY 4'&
43. 178 Dziedziczenie i projektowanie zorientowane obiektowo W
tym przypadku typem dynamicznym zmiennej wskanikowej RT jest
4GEVCPING
44. , zatem zostanie wywoana (zgodnie z naszymi oczekiwaniami)
funkcja wirtualna FTCY zdefiniowana w klasie 4GEVCPING. Domyln
wartoci parametru funkcji 4GEVCP INGFTCY jest )4''0. Poniewa jednak
typem statycznym zmiennej RT jest 5JCRG
45. , domylna warto parametru dla tego wywoania funkcji bdzie
pochodzia z definicji klasy 5JCRG, a nie 4GEVCPING! Otrzymujemy w
efekcie wywoanie skadajce si z nie- oczekiwanej kombinacji dwch
deklaracji funkcji FTCY z klas 5JCRG i 4GEVCPING. Mo esz mi wierzy,
tworzenie oprogramowania zachowujcego si w taki sposb jest ostatni
rzecz, ktr chciaby robi; jeli to Ci nie przekonuje, zaufaj mi na
pewno z takiego zachowania Twojego oprogramowania nie bd zadowoleni
Twoi klienci. Nie musz chyba dodawa, e nie ma w tym przypadku
adnego znaczenia fakt, e RU, RE i RT s wskanikami. Gdyby byy
referencjami, problem nadal by istnia. Jedy- nym istotnym rdem
naszego problemu jest to, e FTCY jest funkcj wirtualn i jedna z jej
domylnych wartoci parametrw zostaa ponownie zdefiniowana w
podklasie. Dlaczego C++ umo liwia tworzenie oprogramowania
zachowujcego si w tak nienatu- ralny sposb? Odpowiedzi jest
efektywno wykonywania programw. Gdyby domylne wartoci parametrw byy
wizane dynamicznie, kompilatory musiayby stosowa dodatkowe
mechanizmy okrelania waciwych domylnych wartoci parametrw funkcji
wirtualnych podczas wykonywania programw, co prowadzioby do
spowolnienia i komplikacji stosowanego obecnie mechanizmu ich
wyznaczania w czasie kompilacji. Decyzj podjto wic wycznie z myl o
szybkoci i prostocie implementacji. Efektem jest wydajny mechanizm
wykonywania programw, ale tak e jeli nie bdziesz stosowa zalece
zawartych w tym sposobie potencjalne nieporozumienia. Sposb 39.
Unikaj rzutowania w d hierarchii dziedziczenia Sposb 39. Unikaj
rzutowania w d hierarchii dziedziczenia W dzisiejszych
niespokojnych czasach warto mie na oku poczynania instytucji
finansowych, rozwa my wic klas protokou (patrz sposb 34.) dla kont
bankowych: ENCUU 2GTUQP ] _ ENCUU $CPM#EEQWPV ] RWDNKE $CPM#EEQWPV
EQPUV 2GTUQP
48. Sposb 39. Unikaj rzutowania w d hierarchii dziedziczenia
179 Wiele bankw przedstawia dzisiaj swoim klientom niezwykle szerok
ofert typw kont bankowych, za my jednak (dla uproszczenia), e
istnieje tylko jeden typ konta bankowego, zwyke konto
oszczdnociowe: ENCUU 5CXKPIU#EEQWPV RWDNKE $CPM#EEQWPV ] RWDNKE
5CXKPIU#EEQWPV EQPUV 2GTUQP
49. RTKOCT[1YPGT EQPUV 2GTUQP
50. LQKPV1YPGT `5CXKPIU#EEQWPV XQKF ETGFKV+PVGTGUV FQFCL
QFUGVMK FQ MQPVC _ Nie jest to mo e zbyt zaawansowane konto
oszczdnociowe, jest jednak w zupeno- ci wystarczajce dla naszych
rozwa a. Bank prawdopodobnie przechowuje list wszystkich swoich
kont, ktra mo e by zaim- plementowana za pomoc szablonu klasy NKUV
ze standardowej biblioteki C++ (patrz sposb 49.). Przypumy, e w
naszym banku taka lista nosi nazw CNN#EEQWPVU: NKUV$CPM#EEQWPV
51. CNN#EEQWPVU YU[UVMKG MQPVC QDU WIKYCPG RTG FCP[ DCPM Jak
wszystkie standardowe pojemniki, listy przechowuj jedynie kopie
umieszczanych w nich obiektw, zatem, aby unikn przechowywania wielu
kopii poszczeglnych obiektw klasy $CPM#EEQWPV, programici
zdecydowali, e lista CNN#EEQWPVU powinna skada si jedynie ze
wskanikw do tych obiektw, a nie samych obiektw repre- zentujcych
konta. Przypumy, e naszym zadaniem jest kolejne przejcie przez
wszystkie konta i doliczenie do nich nale nych odsetek. Mo emy
zrealizowa t usug w nastpujcy sposb: RVNC PKG QUVCPKG UMQORKNQYCPC
LG NK PKIF[ YEG PKGL PKG OKC G FQ E[PKGPKC MQFGO Y[MQT[UVWLE[O
KVGTCVQT[ RCVT PK GL HQT NKUV$CPM#EEQWPV
52. KVGTCVQT R CNN#EEQWPVUDGIKP R CNN#EEQWPVUGPF R ]
53. R ETGFKV+PVGTGUV D F _ Nasze kompilatory szybko
zasygnalizuj, e lista CNN#EEQWPVU zawiera wskaniki do obiektw klasy
$CPM#EEQWPV, a nie obiektw klasy 5CXKPIU#EEQWPV, zatem w ka dej
kolejnej iteracji zmienna R bdzie wskazywaa na obiekt klasy
$CPM#EEQWPV. Oznacza to, e wywoanie funkcji ETGFKV+PVGTGUV jest
nieprawi