C++. 50 efektywnych sposobów na udoskonalenie Twoich programów

59
Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: [email protected] PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ IDZ DO IDZ DO ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG KATALOG KSI¥¯EK KATALOG KSI¥¯EK TWÓJ KOSZYK TWÓJ KOSZYK CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE O NOWOCIACH ZAMÓW INFORMACJE O NOWOCIACH ZAMÓW CENNIK ZAMÓW CENNI K CZYTELNIA CZYTELNIA FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE SPIS TRECI SPIS TRECI DODAJ DO KOSZYKA DODAJ DO KOSZYKA KATALOG ONLINE KATALOG ONLINE C++. 50 efektywnych sposobów na udoskonalenie Twoich programów Pierwsze wydanie ksi¹¿ki „C++. 50 efektywnych sposobów na udoskonalenie twoich programów” zosta³o sprzedane w nak³adzie 100 000 egzemplarzy i zosta³o przet³umaczone na cztery jêzyki. Nietrudno zrozumieæ, dlaczego tak siê sta³o. Scott Meyers w charakterystyczny dla siebie, praktyczny sposób przedstawi³ wiedzê typow¹ dla ekspertów — czynnoci, które niemal zawsze wykonuj¹ lub czynnoci, których niemal zawsze unikaj¹, by tworzyæ prosty, poprawny i efektywny kod. Ka¿da z zawartych w tej ksi¹¿ce piêædziesiêciu wskazówek jest streszczeniem metod pisania lepszych programów w C++, za odpowiednie rozwa¿ania s¹ poparte konkretnymi przyk³adami. Z myl¹ o nowym wydaniu, Scott Meyers opracowa³ od pocz¹tku wszystkie opisywane w tej ksi¹¿ce wskazówki. Wynik jego pracy jest wyj¹tkowo zgodny z miêdzynarodowym standardem C++, technologi¹ aktualnych kompilatorów oraz najnowszymi trendami w wiecie rzeczywistych aplikacji C++. Do najwa¿niejszych zalet ksi¹¿ki „C++. 50 efektywnych sposobów na udoskonalenie twoich programów” nale¿¹: • Eksperckie porady dotycz¹ce projektowania zorientowanego obiektowo, projektowania klas i w³aciwego stosowania technik dziedziczenia • Analiza standardowej biblioteki C++, w³¹cznie z wp³ywem standardowej biblioteki szablonów oraz klas podobnych do string i vector na strukturê dobrze napisanych programów • Rozwa¿ania na temat najnowszych mo¿liwoci jêzyka C++: inicjalizacji sta³ych wewn¹trz klas, przestrzeni nazw oraz szablonów sk³adowych • Wiedza bêd¹ca zwykle w posiadaniu wy³¹cznie dowiadczonych programistów Ksi¹¿ka „C++. 50 efektywnych sposobów na udoskonalenie twoich programów” pozostaje jedn¹ z najwa¿niejszych publikacji dla ka¿dego programisty pracuj¹cego z C++. Scott Meyers jest znanym autorytetem w dziedzinie programowania w jêzyku C++; zapewnia us³ugi doradcze dla klientów na ca³ym wiecie i jest cz³onkiem rady redakcyjnej pisma C++ Report. Regularnie przemawia na technicznych konferencjach na ca³ym wiecie, jest tak¿e autorem ksi¹¿ek „More Effective C++” oraz „Effective C++ CD”. W 1993. roku otrzyma³ tytu³ doktora informatyki na Brown University. Autor: Scott Meyers T³umaczenie: Miko³aj Szczepaniak ISBN: 83-7361-345-5 Tytu³ orygina³u: Effective C++: 50 Specific Ways to Improve Your Programs and Design Format: B5, stron: 248

description

Pierwsze wydanie książki "C++. 50 efektywnych sposobów na udoskonalenie twoich programów" zostało sprzedane w nakładzie 100 000 egzemplarzy i zostało przetłumaczone na cztery języki. Nietrudno zrozumieć, dlaczego tak się stało. Scott Meyers w charakterystyczny dla siebie, praktyczny sposób przedstawił wiedzę typową dla ekspertów -- czynności, które niemal zawsze wykonują lub czynności, których niemal zawsze unikają, by tworzyć prosty, poprawny i efektywny kod. Każda z zawartych w tej książce pięćdziesięciu wskazówek jest streszczeniem metod pisania lepszych programów w C++, zaś odpowiednie rozważania są poparte konkretnymi przykładami. Z myślą o nowym wydaniu, autor opracował od początku wszystkie opisywane w tej książce wskazówki. Wynik jego pracy jest wyjątkowo zgodny z międzynarodowym standardem C++, technologią aktualnych kompilatorów oraz najnowszymi trendami w świecie rzeczywistych aplikacji C++. Do najważniejszych zalet książki "C++. 50 efektywnych sposobów na udoskonalenie twoich programów" należą:* Eksperckie porady dotyczące projektowania zorientowanego obiektowo, projektowania klas i właściwego stosowania technik dziedziczenia* Analiza standardowej biblioteki C++, włącznie z wpływem standardowej biblioteki szablonów oraz klas podobnych do string i vector na strukturę dobrze napisanych programów* Rozważania na temat najnowszych możliwości języka C++: inicjalizacji stałych wewnątrz klas, przestrzeni nazw oraz szablonów składowych* Wiedza będąca zwykle w posiadaniu wyłącznie doświadczonych programistów Książka "C++. 50 efektywnych sposobów na udoskonalenie twoich programów" pozostaje jedną z najważniejszych publikacji dla każdego programisty pracującego z C++.

Transcript of C++. 50 efektywnych sposobów na udoskonalenie Twoich programów

  • 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
  • 16. RU PGY 4GEVCPING FQDTG RU FTCY Y[YQ WLG 4GEVCPINGFTCY 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
  • 19. RC PGY /QFGN% RC HN[ 1MGEKG Y[YQ WLG HWPMEL #KTRNCPGHN[
  • 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
  • 41. RU RT V[RGO F[PCOKEP[O YUMC PKMC RU LGUV VGTC 4GEVCPING
  • 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
  • 46. RTKOCT[1YPGT EQPUV 2GTUQP
  • 47. LQKPV1YPGT XKTVWCN `$CPM#EEQWPV XKTVWCN XQKF OCMG&GRQUKV FQWDNG COQWPV XKTVWCN XQKF OCMG9KVJFTCYCN FQWDNG COQWPV XKTVWCN FQWDNG DCNCPEG EQPUV _
  • 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