Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00386 010315 11037690 na godz. na dobę w sumie
C++. Styl programowania - książka
C++. Styl programowania - książka
Autor: Liczba stron: 224
Wydawca: Helion Język publikacji: polski
ISBN: 83-7361-321-8 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c++ - programowanie
Porównaj ceny (książka, ebook, audiobook).

C++ wspomaga programowanie w dużej skali, pozwalając na precyzyjne wyrażenie współzależności pomiędzy różnymi częściami programu. Dlatego zakres pojęciowy techniki i stylu programowania w C++ wykracza poza tradycyjne jego pojmowanie w odniesieniu do programowania w małej skali, sprowadzającego się do szczegółów kodowania wiersz po wierszu.

Autor dowodzi, że nieprzemyślane stosowanie złożonych i zaawansowanych technik programowania może prowadzić do tworzenia chaotycznych, niezrozumiałych i mętnych konstrukcji, stanowiących zarazem często rozwiązania mniej efektywne, niż prostsze i zrozumiałe konstrukcje alternatywne. Tom Cargill dokonuje przeredagowania licznych programów, stosując techniki pozwalające na udoskonalenie kodu, począwszy od poprawy spójności, po usunięcie zbędnego, nadmiarowego dziedziczenia. Sposób prezentacji zagadnień rozpoczyna się od przeglądu oryginalnego kodu, który możesz samodzielnie ocenić i przeanalizować, rozważając możliwe alternatywne podejścia do przedstawionych zagadnień programistycznych. Te własne przemyślenia możesz następnie porównać z analizami i wnioskami Autora.

Na podstawie przykładów formułowane są uniwersalne reguły i zasady tworzenia kodu i projektowania programów. Zrozumienie i umiejętne stosowanie tych reguł pomoże profesjonalnym programistom projektować i pisać lepsze programy w C++.

Kolejne rozdziały poświęcone są następującym zagadnieniom: Po wprowadzeniu i zilustrowaniu reguł programowania w pierwszych siedmiu rozdziałach, Tom Cargill prezentuje praktyczne studium, w trakcie którego pojedynczy przykładowy program przechodzi kolejne transformacje, które pozwalają poprawić jego ogólną jakość przy jednoczesnym zredukowaniu wielkości kodu. Konkluzję książki stanowi rozdział poświęcony wielokrotnemu dziedziczeniu.

Książka Toma Cargilla to nie tylko cenne źródło wiedzy dla zaawansowanych programistów -- przyda się ona również studentom informatyki i pokrewnych kierunków, zainteresowanych zdobyciem profesjonalnych umiejętności programistycznych.

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

Darmowy fragment publikacji:

IDZ DO IDZ DO PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ SPIS TREĎCI SPIS TREĎCI KATALOG KSI¥¯EK KATALOG KSI¥¯EK KATALOG ONLINE KATALOG ONLINE ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG TWÓJ KOSZYK TWÓJ KOSZYK DODAJ DO KOSZYKA DODAJ DO KOSZYKA CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE ZAMÓW INFORMACJE O NOWOĎCIACH O NOWOĎCIACH ZAMÓW CENNIK ZAMÓW CENNIK CZYTELNIA CZYTELNIA FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: helion@helion.pl C++. Styl programowania Autor: Tom Cargill T³umaczenie: Adam Majczak ISBN: 83-7361-321-8 Tytu³ orygina³u: C++ Programming Style Format: B5, stron: 224 C++ wspomaga programowanie w du¿ej skali, pozwalaj¹c na precyzyjne wyra¿enie wspó³zale¿noġci pomiêdzy ró¿nymi czêġciami programu. Dlatego zakres pojêciowy techniki i stylu programowania w C++ wykracza poza tradycyjne jego pojmowanie w odniesieniu do programowania w ma³ej skali, sprowadzaj¹cego siê do szczegó³ów kodowania wiersz po wierszu. Autor dowodzi, ¿e nieprzemyġlane stosowanie z³o¿onych i zaawansowanych technik programowania mo¿e prowadziæ do tworzenia chaotycznych, niezrozumia³ych i mêtnych konstrukcji, stanowi¹cych zarazem czêsto rozwi¹zania mniej efektywne, ni¿ prostsze i zrozumia³e konstrukcje alternatywne. Tom Cargill dokonuje przeredagowania licznych programów, stosuj¹c techniki pozwalaj¹ce na udoskonalenie kodu, pocz¹wszy od poprawy spójnoġci, po usuniecie zbêdnego, nadmiarowego dziedziczenia. Sposób prezentacji zagadnieñ rozpoczyna siê od przegl¹du oryginalnego kodu, który mo¿esz samodzielnie oceniæ i przeanalizowaæ, rozwa¿aj¹c mo¿liwe alternatywne podejġcia do przedstawionych zagadnieñ programistycznych. Te w³asne przemyġlenia mo¿esz nastêpnie porównaæ z analizami i wnioskami Autora. Na podstawie przyk³adów formu³owane s¹ uniwersalne regu³y i zasady tworzenia kodu i projektowania programów. Zrozumienie i umiejêtne stosowanie tych regu³ pomo¿e profesjonalnym programistom projektowaæ i pisaæ lepsze programy w C++. Kolejne rozdzia³y poġwiêcone s¹ nastêpuj¹cym zagadnieniom: • Abstrakcja —- pojêcia i modele abstrakcyjne • Spójnoġæ • Zbêdne dziedziczenie • Funkcje wirtualne • Przeci¹¿anie operatorów • Nak³adki typu „wrapper” • Efektywnoġæ Po wprowadzeniu i zilustrowaniu regu³ programowania w pierwszych siedmiu rozdzia³ach, Tom Cargill prezentuje praktyczne studium, w trakcie którego pojedynczy przyk³adowy program przechodzi kolejne transformacje, które pozwalaj¹ poprawiæ jego ogóln¹ jakoġæ przy jednoczesnym zredukowaniu wielkoġci kodu. Konkluzjê ksi¹¿ki stanowi rozdzia³ poġwiêcony wielokrotnemu dziedziczeniu. Ksi¹¿ka Toma Cargilla to nie tylko cenne ĥród³o wiedzy dla zaawansowanych programistów — przyda siê ona równie¿ studentom informatyki i pokrewnych kierunków, zainteresowanych zdobyciem profesjonalnych umiejêtnoġci programistycznych. Spis treści Przedmowa.................................................................................................................7 Wprowadzenie ...........................................................................................................9 1. Abstrakcja ................................................................................................................15 Przykład techniki programowania: ceny komputerów...................................................t................................... 15 Poszukiwanie wspólnego pojęcia abstrakcyjnego......t...................................................t.................................... 19 Różnice pomiędzy klasami............................t...................................................t................................................. 22 Powtórnie wprowadzamy do programu dziedziczenie....t...................................................t............................... 26 Wyeliminowanie typów wyliczeniowych ...................................................t...................................................... 27 Podsumowanie ...................................................t...................................................t............................................ 30 Bibliografia ...................................................t...................................................t................................................. 31 Ćwiczenie...................................................t...................................................t.................................................... 31 2. Spójność kodu..........................................................................................................35 Przykład techniki programowania: klasa string ...................................................t............................................. 36 Precyzyjnie zdefiniowany stan obiektu...................................................t.......................................................... 37 Spójność fizycznego stanu obiektu ...................................................t................................................................ 38 Niezmienne warunki, czyli inwariancja klas..........t...................................................t........................................ 39 Konsekwentne stosowanie dynamicznego zarządzania pamięcią...................................................t.................. 41 Zwolnienie dynamicznie przyporządkowanej pamięci ...................................................t.................................. 43 Przykład techniki programowania: drugie podejście do tego samego problemu.............................................. 44 Podsumowanie ...................................................t...................................................t............................................ 50 Bibliografia ...................................................t...................................................t................................................. 51 Ćwiczenia...................................................t...................................................t.................................................... 51 3. Zbędne dziedziczenie..............................................................................................55 Przykład techniki programowania: stos ...................................................t......................................................... 55 Reguły widoczności nazw w procesie dziedziczenia...................................................t..................................... 59 Relacje tworzone poprzez dziedziczenie...................................................t........................................................ 60 4 SPIS TREŚCI Enkapsulacja.......................................t...................................................t............................................................ 65 Interfejs a implementacja ...................................................t...................................................t............................ 67 Zastosowanie szablonów...................................................t...................................................t............................. 71 Podsumowanie ...................................................t...................................................t............................................ 73 Bibliografia ...................................................t...................................................t................................................. 73 Ćwiczenie...................................................t...................................................t.................................................... 74 4. Funkcje wirtualne ...................................................................................................75 Przykład techniki programowania: problem pojazdów i garaży...................................................t.................... 75 Spójność kodu ...................................................t...................................................t............................................. 79 Destruktory klas bazowych ...................................................t...................................................t......................... 81 Dziedziczenie ...................................................t...................................................t.............................................. 82 Sprzężenie ...................................................t...................................................t................................................... 85 Podsumowanie ...................................................t...................................................t............................................ 91 Bibliografia ...................................................t...................................................t................................................. 92 Ćwiczenie...................................................t...................................................t.................................................... 92 5. Przeciążanie operatorów .........................................................................................93 Przeciążanie operatorów — podstawy ...................................................t........................................................... 93 Przykład techniki programowania: tablica plików FileArray ...................................................t........................ 98 Dziedziczenie implementacji ...................................................t....................................................................... 105 Programistyczny dylemat — jak lepiej: przeciążone operatory czy metody .................................................. 110 Podsumowanie ...................................................t...................................................t.......................................... 112 Bibliografia ...................................................t...................................................t............................................... 112 Ćwiczenia...................................................t...................................................t.................................................. 112 6. Klasy otaczające (ang. wrappers) .........................................................................113 Biblioteka C ...................................................t...................................................t.............................................. 113 Przykład techniki programowania: klasa otaczająca dla struktury dirent w C++........................................... 114 Wiele obiektów klasy Directory...................................................t................................................................... 116 Gdy zawodzi konstruktor ...................................................t...................................................t.......................... 119 Publiczny dostęp do stanu obiektu...................................................t............................................................... 121 Dodatkowy argument służący obsłudze błędów ...................................................t.......................................... 123 Podsumowanie ...................................................t...................................................t.......................................... 127 Bibliografia ...................................................t...................................................t............................................... 128 Ćwiczenie...................................................t...................................................t.................................................. 128 7. Efektywność...........................................................................................................129 Przykład techniki programowania: klasa BigInt ...................................................t.......................................... 130 Egzaminujemy klasę BigInt ...................................................t...................................................t...................... 136 Długość dynamicznych łańcuchów znaków ...................................................t................................................ 138 Liczba dynamicznie zapamiętywanych łańcuchów znaków...................................................t........................ 140 Kod klienta ...................................................t...................................................t................................................ 144 Modyfikujemy klasę BigInt ...................................................t...................................................t...................... 145 SPIS TREŚCI 5 Podsumowanie ...................................................t...................................................t.......................................... 151 Bibliografia ...................................................t...................................................t............................................... 152 Ćwiczenia...................................................t...................................................t.................................................. 152 8. Studium praktyczne ..............................................................................................153 Przykład techniki programowania: Finite State Machine, czyli automat stanów skończonych ..................... 153 Zainicjowanie...................................................t...................................................t............................................ 158 Sprzężenia ...................................................t...................................................t................................................. 165 Spójność ...................................................t...................................................t.................................................... 169 Moduły kontra abstrakcyjne typy danych ...................................................t.................................................... 172 Wartość kontra zachowanie ...................................................t...................................................t...................... 175 Uogólnienie ...................................................t...................................................t............................................... 180 Bibliografia ...................................................t...................................................t............................................... 184 Ćwiczenia...................................................t...................................................t.................................................. 185 9. Dziedziczenie wielokrotne....................................................................................187 Niejednoznaczności w mechanizmie wielokrotnego dziedziczenia...................................................t............. 187 Skierowane niecykliczne grafy hierarchii dziedziczenia ...................................................t............................. 189 Eksplorujemy wirtualne klasy bazowe...................................................t......................................................... 193 Przykład techniki programowania: klasa Monitor ...................................................t....................................... 200 Przykład techniki programowania: wirtualna klasa bazowa ...................................................t........................ 204 Protokół powiadomień zwrotnych i przydatne wielokrotne dziedziczenie...................................................t.. 210 Podsumowanie ...................................................t...................................................t.......................................... 213 Bibliografia ...................................................t...................................................t............................................... 213 Ćwiczenia...................................................t...................................................t.................................................. 213 10. Zbiorcza lista reguł ...............................................................................................215 Rozdział 1.: „Abstrakcja”...................................................t...................................................t.......................... 215 Rozdział 2.: „Spójność kodu” ...................................................t...................................................................... 215 Rozdział 3.: „Zbędne dziedziczenie” ...................................................t........................................................... 216 Rozdział 4.: „Funkcje wirtualne” ...................................................t................................................................. 216 Rozdział 5.: „Przeciążanie operatorów”................t...................................................t....................................... 216 Rozdział 6.: „Klasy otaczające (ang. wrappers)”...................................................t......................................... 216 Rozdział 7.: „Efektywność” ...................................................t......................................................................... 217 Rozdział 8.: „Studium praktyczne”...................................................t.............................................................. 217 Rozdział 9.: „Dziedziczenie wielokrotne” ...................................................t................................................... 217 Skorowidz ..............................................................................................................219 3 Zbędne dziedziczenie Chociaż w rozdziale 2. uważnie rozróżnialiśmy interfejs klasy i implementację klasy, nie czynili- śmy tego w kontekście dziedziczenia. Aby dokładnie zrozumieć relację dziedziczenia pomiędzy klasą pochodną a jej klasą bazową, musimy rozpatrywać część interfejsową i część implementacyjną klasy w sposób odrębny. W tym rozdziale przeanalizujemy przypadek, który z pozoru wydaje się być naturalną kandydaturą do zastosowania dziedziczenia. Mimo to, dokładniejsza analiza dziedzicze- nia i implementacji klas bazowej i pochodnej spowodruje w efekcie znaczącą modyfikację kodu. Przykład techniki programowania: stos Na listingu 3.1 pokazano program, w którym zostały zdefiniowane klasy JCT5VCEM (dosł. stos prze- znaczony na znaki) oraz +PV5VCEM (dosł. stos przeznaczony na liczby typu KPV). Przeanalizujmy i oceń- my te klasy. Czy widać tu uogólnienie pojęć abstrakcyjnych? Czy dziedziczenie działa w sposób poprawny? Czy dziedziczenie zostało wykorzystane we rwłaściwy sposób? LISTING 3.1. Oryginalna wersja klas Stack, CharStack oraz IntSta1ck KPENWFGCUUGTVJ KPENWFGUVTKPIJ ENCUU5VCEM]MNCUCDCQYCUVQU KPVVQRYKGTEJQđGMUVQUW KPVUKGTQOKCT RTQVGEVGFFQUVúRPGV[NMQFNCMNCURQEPJQFP[EJ XQKF XGEVCDNKECYUMCļPKMÎY YGMVQT RWDNKERWDNKEP[KPVGTHGLUMNCU[DCPQYGL 5VCEM KPVU MQPUVTWMVQTQIÎNP[ `5VCEM FGUVTWMVQTMNCU[DCQYGL XQKF RWUJ OGVQFC QFđÎľPCUVQU XQKF RQR OGVQFC FGLOKLGUVQUW _ 56 3. ZBĘDNE DZIEDZICZENIE 5VCEM5VCEM KPVU FGHKPKELCMQPUVTWMVQTC ] XGEPGYXQKF =UKGU? VQR _ 5VCEM`5VCEM ] FGNGVG=?XGEYCNPKCRCOKúèCLOQYCPæRTGPVCDNKEúYUMCļPKMÎY _ XQKF 5VCEMRWUJ ] CUUGTV VQRUKG  TGVWTPXGE=VQR ? _ XQKF 5VCEMRQR ] CUUGTV VQR   TGVWTPXGE=VQR? _ EQPUVKPVFGHCWNV5VCEMFQO[ħNP[TQOKCTUVQUWP ENCUU JCT5VCEMRWDNKE5VCEM] EJCT FCVCRT[YCVP[YUMCļPKMPFQFCP[EJ RWDNKE  JCT5VCEM MQPUVTWMVQTFQO[ħNPP[  JCT5VCEM KPVUKG   JCT5VCEM KPVUKGEJCT KPKV  ` JCT5VCEM  XQKFRWUJ EJCT  EJCTRQR  _ JCT5VCEM JCT5VCEM 5VCEM FGHCWNV5VCEM ] FCVCPGYEJCT=FGHCWNV5VCEM? HQT KPVKKFGHCWNV5VCEM K XGE=K?FCVC=K? _ JCT5VCEM JCT5VCEM KPVUKG 5VCEM UKG ] FCVCPGYEJCT=UKG? HQT KPVKKUKG K XGE=K?FCVC=K? _ PRZYKŁAD TECHNIKI PROGRAMOWANIA: STOS 57 JCT5VCEM JCT5VCEM KPVUKGEJCT KPKV 5VCEM UKG ] FCVCPGYEJCT=UKG? HQT KPVKKUKG K XGE=K?FCVC=K? HQT KKUVTNGP KPKV  K  EJCT 5VCEMRWUJ KPKV=K? _ JCT5VCEM` JCT5VCEM ] FGNGVG=?FCVC _ XQKF JCT5VCEMRWUJ EJCTF ]  EJCT 5VCEMRWUJ F _ EJCT JCT5VCEMRQR ] TGVWTP EJCT 5VCEMRQR  _ ENCUU+PV5VCEMRWDNKE5VCEM] KPV FCVCRT[YCVP[YUMCļPKMFQFCPP[EJ RWDNKE +PV5VCEM MQPUVTWMVQTFQO[ħNP[ +PV5VCEM KPVUKG  `+PV5VCEM  XQKFRWUJ KPV  KPVRQR  _ +PV5VCEM+PV5VCEM 5VCEM FGHCWNV5VCEM ] FCVCPGYKPV=FGHCWNV5VCEM? HQT KPVKKFGHCWNV5VCEM K XGE=K?FCVC=K? _ +PV5VCEM+PV5VCEM KPVUKG 5VCEM UKG ] FCVCPGYKPV=UKG? HQT KPVKKUKG K XGE=K?FCVC=K? _ +PV5VCEM`+PV5VCEM ] 3. ZBĘDNE DZIEDZICZENIE 58 FGNGVG=?FCVC _ XQKF+PV5VCEMRWUJ KPVF ]  KPV 5VCEMRWUJ F _ KPV+PV5VCEMRQR ] TGVWTP KPV 5VCEMRQR  _ Natychmiastowa, odruchowa reakcja wielu Czytelników na samo hasło JCT5VCEM oraz +PV 5VCEM może polegać na stwierdzeniu, że takie klasy powinny zostać skonstruowane przy zastoso- waniu typów sparametryzowanych, czyli szablonów (ang. templates), zgodnie z tym, co proponuje standard ANSI C++ (patrz: Ellis i Stroustrup). Ignorujemy tę możliwość i skoncentrujemy się na programie w takiej postaci, w jakiej został napisany. A to z dwóch powodów. Po pierwsze, ważne jest zrozumienie, przy wykorzystaniu najbardziej podstawowych konstrukcji języka C++, w jaki sposób powstała struktura tego programu, zanim jeszcze zaczniemy rozważać, w jaki sposób można by go udoskonalić, wykorzystując rozszerzenie języka. Pewne błędy w rozumowaniu, które dopro- wadziły do powstania tego programu, mogłyby łatwo wystąpić w bardziej kłopotliwej sytuacji, gdy użycie typów parametryzowanych (szablonów) nie dałoby nam żadnej alternatywy. Po drugie, w chwili pisania tej książki, implementacje typów parametryzowanych nie były jeszcze szeroko rozpowszechnione i dostępne. Co więcej, należało się liczyć z tym, że jeszcze przez pewien czas szablony nie zostaną poprawnie zaadaptowane do języka C++. Przedwczesne byłoby rozważanie technik programowania w odniesieniu do typów parametryzowanych przed nagromadzeniem sto- sownego praktycznego doświadczenia, co pozwoliłoby na rporadnictwo, jak właściwie i efektywnie stosować te techniki. Wersja kodu napisana z zastosowaniem szablonów została podana na końcu tego rozdziału. Istota tej konstrukcji kodu sprowadza się do tego, że obydwie klasy: JCT5VCEM oraz +PV5VCEM reprezentują stos (pierwsza dla znaków, druga dla liczb całkowitych), zatem obie mają wspólną klasę bazową 5VCEM (po prostu „Stos”, bez skonkretyzowania szczegółów). Taka hierarchia dzie- dziczenia została schematycznie przedstawiona na ryrsunku 3.1. RYSUNEK 3.1. Hierarchia dziedziczenia w programie z listingu 3.1 Musimy popatrzeć na ten program znacznie uważniej, by dostrzec, że takie dziedziczenie nie jest niezbędne. Co więcej, okazuje się mylące i wręcz powinno zostać wyeliminowane. W istocie, zastosowanie dziedziczenia z specyfikatorem dostępu RWDNKE w tym programie tworzy niebez- pieczną lukę w enkapsulacji takich stosów. REGUŁY WIDOCZNOŚCI NAZW W PROCESIE DZIEDZICZENIA 59 Reguły widoczności nazw w procesie dziedziczenia Dziedziczenie może następować z użyciem specyfikatorów dostępu RWDNKE, RTKXCVG, RTQVGEVGF. Sposób dziedziczenia decyduje o widoczności i dostępności nazw (ang. inheritance scope rules). Publiczny interfejs klasy bazowej 5VCEM jest następujący: RWDNKE 5VCEM KPVU  `5VCEM  XQKF RWUJ  XQKF RQR  _ Patrząc na klasy pochodne, widzimy tam metody o tych samych nazwach (RQR oraz RWUJ ), co w klasie bazowej. Zwróćmy uwagę, że metody 5VCEMRWUJ oraz 5VCEMRQR nie są funkcjami wirtualnymi. Zauważmy także, że typy argumentów tych metod w klasach pochod- nych nie są zgodne z typami argumentów odpowiednich metod w klasie bazowej. Na przykład, metoda 5VCEMRWUJ jest bezargumentowa, podczas gdy metoda +PV5VCEMRWUJ KPV pobiera jeden argument typu KPV. W przypadku takich funkcji reguły widoczności nazw C++ mówią, że metoda zawarta w klasie pochodnej przesłania metodę o tej samej nazwie zawartą w klasie bazowej, ponieważ klasa pochodna wprowadza nowy poziom widoczności nazw (ang. new scope level). Re- zultat tych reguł widoczności można wyraźnie dostrzecr na następującym przykładzie: ENCUU$CUG]MNCUCDCQYC RWDNKE XQKFH HNQCV  XQKFI HNQCV  _ ENCUU GTKXGFRWDNKE$CUG]MNCUCRQEJQFPC RWDNKE XQKFH KPV  _ KPVOCKP  GTKXGFF FH  Y[YQđCPKG GTKXGFH   FI  Y[YQđCPKG$CUGI   Wyraźnie widać, że w tym przypadku (przy dziedziczeniu) funkcja — metoda o nazwie H nie jest przeciążona (ang. not overloaded). Klasa pochodna definiuje funkcję o nazwie H , a zatem wyrażenie FH  musi jednoznacznie wywołać metodę H zdefiniowaną w klasie pochodnej, a nie w klasie bazowej. Natomiast wyrażenie FI  wywołuje metodę z klasy bazowej, ponieważ w klasie pochodnej nie została zdefiniowana własna wersja metody o nazwie I . Gdy kompilator C++ poszukuje w celu wywołania metody I , najpierw przeszukiwana jest klasa pochodna, jeśli w klasie pochodnej nie zostanie odszukana żadna „pasująca” metoda, w drugiej kolejności poszu- kiwanie nastąpi w klasie bazowej. Zatem wywołanie metody I wobec obiektu klasy pochodnej spowoduje w istocie zastosowanie wersji $CUGI z klasy bazowej, ale wywołanie metody H będzie oznaczać GTKXGFH , a nie $CUGH , która została przesłonięta i stała się w ten sposób niewidoczna. 60 3. ZBĘDNE DZIEDZICZENIE Publiczny interfejs klasy +PV5VCEM jest następujący: RWDNKE +PV5VCEM  +PV5VCEM KPVUKG  `+PV5VCEM  XQKFRWUJ KPV  KPVRQR  _ Zgodnie z zasadami widoczności (dostępności) nazw opisanymi powyżej, kod klienta może manipulować obiektami klas +PV5VCEM lub JCT5VCEM przy użyciu wywołań ich własnych metod RWUJ oraz RQR , a nie poprzez metody odziedziczone po klasie bazowej 5VCEM. Klasy pochodne +PV5VCEM oraz JCT5VCEM dziedziczą publiczny interfejs po klasie bazowej 5VCEM, ale odziedziczone metody przesłaniają własnymi wersjami tych funkcji. Powrócimy jeszcze do tego zagadnienia w dal- szej części tego rozdziału. Relacje tworzone poprzez dziedziczenie Przyjrzyjmy się na razie bliżej relacjom tworzonym poprzez dziedziczenie. Klasa bazowa 5VCEM zapewnia, że w każdej jej klasie pochodnej będzie zaimplementowany mechanizm indeksujący (ang. indexing service). Odpowiedzialna jest za to jednowymiarowa tablica (czyli wektor) o na- zwie XGE. Tablica ta jest typu chronionego i składa się ze wskaźników do typu XQKF. Klasa 5VCEM zarządza także prywatnym polem danych KPV VQR (wierzchołek stosu) pozwalającą na obsługę tablicy w odpowiedzi na wywołania metod RWUJ oraz RQR z klas pochodnych. Klasy pochodne tworzą własne tablice — wektory wskazywane poprzez wskaźniki FCVC dla wartości przechowy- wanych na stosie i inicjują te wektory tak, że dla wszystkich wartości indeksu tablicy K (oczywi- ście, w granicach wektora) element XGE=K? jest wskaźnikiem do elementu FCVC=K?. Wskaźniki typu XQKF zwracane przez metody 5VCEMRWUJ oraz 5VCEMRWUJ mówią klasie pochodnej, na któ- rym elemencie tablicy FCVC mają być wykonywane odpowiednio operacje RWUJ (odłóż na stos) oraz RQR (zdejmij ze stosu). Struktura danych została przedstawiona na rysunku 3.2. Zwróćmy uwagę na spójność układu wskaźników. Taka struktura danych zawiera, niestety, niewiele informacji. Konstruktory klas po- chodnych zawierają i wykonują instrukcję: XGE=K?FCVC=K? dla każdego z elementów wskazywanych w tablicy FCVC=?, aby poprawnie zainicjować wskaźniki wchodzące w skład tablicy XGE=?. Po takim zadziałaniu konstruktora i zainicjowaniu, te wskaźniki nigdy nie ulegają zmianie. Klasa bazowa odsyła te wskaźniki z powrotem, by „powiedzieć” klasie pochodnej, gdzie (pod jakim adresem) znajdują się dane, do których można się odwoływać. Coś tu jest jednak nie tak. Chyba trochę za dużo wskaźników w porównaniu z niewielką ilością rzeczywi- stej, użytecznej informacji. Gdy skoncentrujemy się na wymuszonej konwersji typu (ang. type casting) w kodzie metody 5VCEMRQR , zobaczymy tę samą sytuację z innej perspektywy. RELACJE TWORZONE POPRZEZ DZIEDZICZENIE 61 RYSUNEK 3.2. Relacja wskaźników z wektora vec do elementów tablicy data KPV+PV5VCEMRQR ] TGVWTP KPV 5VCEMRQR  _ Ta wymuszona konwersja powoduje przekształcenie wskaźnika do typu XQKF zwróconego przez metodę 5VCEMRQR na wskaźnik do typu KPV w celu odwołania się do elementu tablicy wska- zywanej przez wskaźnik +PV5VCEMFCVC. Aby uniknąć niespodzianek i niebezpieczeństw kryją- cych się w mechanizmach rzutowania, zawsze lepiej programować bez wymuszania konwersji typu. Ta konwersja może być łatwo wyeliminowana. Gdy to zrobimy, wyjdzie na jaw problem, który sprawia wektor XGE. Ogólnie rzecz biorąc, logika i kolejność działań jest tru następująca: metoda 5VCEMRQR zwraca wskaźnik XGE=K?, wskaźnik XGE=K? wskazuje element tablicy FCVC=K?, metoda +PV5VCEMRQR zwraca FCVC=K?. • • • Żadna konwersja typów nie byłaby tu potrzebna, gdyby funkcja 5VCEMRQR zwracała sam indeks K, bezpośrednio, zamiast wskaźnika XGE=K?. Mając wartość indeksu K, metoda +PV5VCEM RQR bezpośrednio odwoływałaby się do elementu tablicy FCVC=K?, bez żadnej konwersji typu. Metody RWUJ oraz RQR należące do klasy bazowej 5VCEM mogłyby zwracać pojedynczą liczbę całkowitą (indeks), której obiekt klasy pochodnej rzeczywiście potrzebuje, zamiast wskaźnika typu XQKF, który przed użyciem musi zostać poddany konwersji typu. Taka prostsza wersja klasy 5VCEM została pokazana na listingu 3.2. Zwróćmy uwagę, że metody RWUJ oraz RQR należące do klas pochod- nych +PV5VCEM oraz JCT5VCEM w tej wersji już nie stosują wymuszonej konwersji typu. Zauważmy także, że nazwa 5VCEM została zmieniona na 5VCEM+PFGZ, co lepiej opisuje to pojęcie abstrakcyjne. LISTING 3.2. Uproszczona klasa abstrakcji „Indeksacja stosu” KPENWFGCUUGTVJ KPENWFGUVTKPIJ ENCUU5VCEM+PFGZ]MNCUCDCQYCKPFGMUCELCPUVQUW RT[YCVPGRQNCFCP[EJ 62 3. ZBĘDNE DZIEDZICZENIE KPVVQRVQRVQ YKGTEJQđGMUVQUPW KPVUKGUKGVQTQOKCTUVQUW RWDNKERWDNKEP[KPVGTHGLUMNCU[DPCQYGL 5VCEM+PFGZ KPVU MQPUVTWMVQTQIÎNP[ `5VCEM+PFGZ FGUVTWMVQTMNCU[DCQYGL KPVRWUJ OGVQFC QFđÎľPCUVQU KPVRQR OGVQFC FGLOKLGUVQUW _ 5VCEM+PFGZ5VCEM+PFGZ KPVU ] UKGU VQR _ 5VCEM+PFGZ`5VCEM+PFGZ ]_RWUV[FGUVTWMVQT KPV5VCEM+PFGZRWUJ ] CUUGTV VQRUKG  TGVWTPVQR  _ KPV5VCEM+PFGZRQR ] CUUGTV VQR   TGVWTPVQR _ EQPUVKPVFGHCWNV5VCEMFQO[ħNP[TQOKCTUVQUW ENCUU JCT5VCEMRWDNKE5VCEM+PFGZ] EJCT FCVCRT[YCVP[YUMCļPKMFPQFCP[EJ RWDNKE  JCT5VCEM MQPUVTWMVQTFQO[ħNPP[  JCT5VCEM KPVUKG   JCT5VCEM KPVUKGEJCT KPKV  ` JCT5VCEM  XQKFRWUJ EJCT  EJCTRQR  _ JCT5VCEM JCT5VCEM 5VCEM+PFGZ FGHCWNV5VCEM ] FCVCPGYEJCT=FGHCWNV5VCEM? _ JCT5VCEM JCT5VCEM KPVUKG 5VCEM+PFGZ UKG ] FCVCPGYEJCT=UKG? _ RELACJE TWORZONE POPRZEZ DZIEDZICZENIE 63 JCT5VCEM JCT5VCEM KPVUKGEJCT KPKV 5VCEM+PFGZ UPKG ] FCVCPGYEJCT=UKG? HQT KPVKKUVTNGP KPKV  K FCVC=5VCEM+PFGZRWUJ ?KPKV=K? _ JCT5VCEM` JCT5VCEM ] FGNGVG=?FCVC _ XQKF JCT5VCEMRWUJ EJCTF ] FCVC=5VCEM+PFGZRWUJ ?F _ EJCT JCT5VCEMRQR ] TGVWTPFCVC=5VCEM+PFGZRQR ? _ ENCUU+PV5VCEMRWDNKE5VCEM+PFGZ] KPV FCVCRT[YCVP[YUMCļPKMFQFCP[EJ RWDNKE +PV5VCEM MQPUVTWMVQTFQO[ħNP[ +PV5VCEM KPVUKG  `+PV5VCEM  XQKFRWUJ KPV  KPVRQR  _ +PV5VCEM+PV5VCEM 5VCEM+PFGZ FGHCWNV5VCEM ] FCVCPGYKPV=FGHCWNV5VCEM? _ +PV5VCEM+PV5VCEM KPVUKG 5VCEM+PFGZ UKG ] FCVCPGYKPV=UKG? _ +PV5VCEM`+PV5VCEM ] FGNGVG=?FCVC _ XQKF+PV5VCEMRWUJ KPVF ] FCVC=5VCEM+PFGZRWUJ ?F _ 64 3. ZBĘDNE DZIEDZICZENIE KPV+PV5VCEMRQR ] TGVWTPFCVC=5VCEM+PFGZRQR ? _ Zastosowany tu nowy interfejs klasy bazowej 5VCEM+PFGZ lepiej odpowiada abstrakcyjnemu pojęciu „indeksacja stosu”, czyli tym funkcjom, które rzeczywiście zapewnia. Wspólna część abs- trakcyjna — to obsługa indeksacji, mówiąca klasie pochodnej, gdzie znajdują się dane, do których można się odwoływać. Informacja o tym, w jaki sposób działa i zachowuje się stos, pozostaje ukryta przed klasą bazową 5VCEM. Informacja o elementach znajdujących się rzeczywiście na poszczegól- nych stosach (obiektach klas pochodnych) zostaje udostępniona klasom pochodnym. Jedyna komu- nikacja następuje tu w zakresie indeksacji. Bez żadnego ograniczenia możliwości funkcjonalnych, taki abstrakcyjny stos może zostać zaimplementowany prościej. Jego zakres odpowiedzialności zostanie ograniczony do obsługi indeksacji w taki sposób, że inna, odrębna klasa może zapewnić mechanizm obsługi stosu. ► Poszukujmy prostych pojęć abstrakcyjnych. Tablica wskaźników 5VCEMXGE jest nadmiarowa (stanowi redundancję), dlatego zniknęła z nowej wersji klasy 5VCEM+PFGZ. Każda z klas pochodnych takiej klasy bazowej otrzymuje wszystkie in- formacje, których potrzebuje w postaci indeksu zwracanego poprzez wywołane metody należące do klasy bazowej: 5VCEM+PFGZRWUJ oraz 5VCEM+PFGZRQR . Nie ma żadnego racjonalnego uza- sadnienia, by w klasie bazowej przechowywać wskaźniki do danych znajdujących się w tablicach deklarowanych w klasach pochodnych. Struktura danych wygląda teraz tak, jak pokazano na ry- sunku 3.3. Takie uproszczenie programu powoduje jednocześnie, że staje się on bardziej efektyw- ny z punktu widzenia wykorzystania pamięci. Wyeliminowanie tablicy 5VCEMXGE gwałtownie zmniejsza ilość pamięci wymaganą do utworzenia stosu. Przy typowej, 32-bitowej architekturze, gdy liczby całkowite typu KPV zajmują po 4 bajty pamięci każda i przy 4-bajtowych wskaźnikach, obiekt klasy +PV5VCEM zajmuje teraz na stercie dwukrotnie mniej pamięci, niż było to w wersji ory- ginalnej. Na każdy element przypadają 4 bajty zamiast 8. Obszar pamięci zajmowany na stercie przez obiekt klasy JCT5VCEM należy podzielić przez współczynnik 5. Zamiast 5 bajtów na każdy znak na stosie przypada teraz tylko 1 bajt. RYSUNEK 3.3. Uproszczona struktura danych ENKAPSULACJA Enkapsulacja 65 Taka nowa reprezentacja abstrakcyjnego pojęcia stosu jest prostsza, a sam program jest krótszy i wykorzystuje mniej pamięci. Relacje wprowadzone poprzez mechanizm dziedziczenia pomiędzy klasą bazową 5VCEM+PFGZ a jej klasami pochodnymi nie zostały jeszcze do końca wyjaśnione. Wcze- śniej w tym rozdziale zasygnalizowano, że metody RWUJ oraz RQR należące do klas pochodnych przesłaniają metody o tych samych nazwach należące do klasy bazowej. Jeśli jednak stosuje się pełne, rozwinięte nazwy funkcji, np.: 5VCEM+PFGZRWUJ i odpowiednio 5VCEM+PFGZRQR , te me- tody mogą nadal być wywołane w zakresie widoczności nazw utworzonym przez klasy pochodne, przez obiekty klas +PV5VCEM lub JCT5VCEM. W istocie, metody 5VCEM+PFGZRWUJ QTC 5VCEM+P FGZRQR są właśnie w taki sposób wywoływane w kodzie przez odpowiednie metody należące do klas pochodnych. Metody z klasy bazowej, 5VCEM+PFGZRWUJ oraz 5VCEM+PFGZRQR , mogą zostać wywoła- ne nawet bez potrzeby sięgania do operatora widoczności . Jeśli do obiektu klasy pochodnej +PV5VCEM lub JCT5VCEM odwołujemy się za pośrednictwem wskaźnika do obiektu klasy bazowej 5VCEM+PFGZ albo za pośrednictwem referencji do obiektu klasy 5VCEM+PFGZ, to RWUJ i RQR oznaczają wywołania metod należących do klasy bazowej. Problem dostępności metod należących do klas bazowych to bardzo poważne zagadnienie (a w tym przypadku — naruszenie) z punktu widzenia enkapsulacji stosów. Przy takiej konstrukcji programu możliwe jest zamierzone bądź omyłkowe wprowadzenie obiektu „stos” w stan niespójny logicznie poprzez odwołanie z innych części pro- gramu. Poniższa funkcja prezentuje trzy sposoby narusrzenia hermetyzacji obiektu klasy +PV5VCEM. XQKF8KQNCVG ] +PV5VCEMU U5VCEM+PFGZRWUJ RWDNKEPGRQNGRWDNKEPGLPMNCU[DCQYGL 5VCEM+PFGZ RU R RWUJ Y[YQđCPKG5VCEM+PFGZRWUJ 5VCEM+PFGZTU TRWUJ Y[YQđCPKG5VCEM+PFGZRWUJ P _ Poprzez bezpośrednie wywołanie publicznych składowych klasy bazowej z kodu klienta, funk- cja 8KQNCVG (dosł. „naruszająca”) zwiększa indeks stosu bez jednoczesnego dostarczenia jakiej- kolwiek wartości przeznaczonej do umieszczenia na stosie. Efekt jest taki, że stos pozornie rośnie o jeden nowy element, ale skoro w chwili wykonania operacji „push” nie zostaje dostarczona żadna wartość do umieszczenia na stosie, to wartość zwracana przez metodę +PV5VCEMRQR pozostaje nieokreślona. Ta wartość pozornie umieszczona na stosie pozostanie tą wartością, która znajdo- wała się w pamięci przeznaczonej dla danego elementu tablicy — poprzednio, w chwili wykona- nia operacji RWUJ. Jak widać, kod klienta może bezpośrednio manipulować implementacją stosu, co może dopro- wadzić obiekt — stos — do stanu niezdefiniowanego. Enkapsulacja stosu (rozumiana jako jego od- izolowanie od kodu klienta) zostaje naruszona. Ten problem nie powstał nagle, w momencie naszej modyfikacji kodu w celu wyeliminowania tablicy zawierającej wskaźniki w klasie bazowej. Ta luka w enkapsulacji występowała już w oryginalnym kodzie rwyjściowym. Należałoby ją zlikwidować. Jest tam jeszcze inny problem związany z dziedziczeniem. Destruktor klasy bazowej nie zo- stał zadeklarowany jako funkcja wirtualna. Jeśli obiekt klasy +PV5VCEM zostanie utworzony w spo- sób dynamiczny, a następnie zostanie usunięty operatorem FGNGVG, kasującym wskaźnik do klasy bazowej, to zostanie wywołany tylko destruktor klasy bazowej. Pod tym względem destruktory 66 3. ZBĘDNE DZIEDZICZENIE zachowują się tak samo, jak inne metody. Rodzaj destruktora (tj. jego przynależność do określonej klasy) jest determinowany poprzez typ wskaźnika (typ wartości wskazywanej przez dany wskaźnik użyty w momencie deklaracji). +PV5VCEM RPGY+PV5VCEM  5VCEM+PFGZ SR  FGNGVGSURQYQFWLGY[YQđCPKGV[NMQ5VCEM+PFGZ`P5VCEM+PFGZ Skoro destruktor klasy pochodnej nie może zostać wywołany, programy (kod klienta) posłu- gujące się klasą +PV5VCEM lub JCT5VCEM są potencjalnie zagrożone występowaniem „wycieków pamięci”. Wyciek pamięci omawiany w rozdziale 2. na przykładzie klasy UVTKPI występował dla- tego, że pominięto pożądany operator FGNGVG w kodzie metody. W tym przypadku „wyciek pamięci” wystąpi nawet wtedy, gdy destruktor w klasie pochodnej będzie całkowicie poprawny. Operator FGNGVG użyty w kodzie destruktora klasy pochodnej może zostać wykonany tylko wtedy, gdy ten destruktor zostanie wywołany. Ten destruktor nie zostanie wywołany, jeśli obiekt klasy pochodnej będzie usuwany poprzez skasowanie wskaźnika do klasy bazowej (wskaźniki do klas bazowych mogą wskazywać także obiekty klas pochodnych i o taką sytuację tu chodzi). Jeśli wektor przy- dzielony dla danych stosu nie zostanie poprawnie usunięty, a jego pamięć zwolniona i zwrócona do systemu, w miarę działania programu będzie wzrastać ilość zajętej, choć bezużytecznej pamięci na stercie, co może doprowadzić nawet do przepełnienia pamięci przeznaczonej na stertę. Wycieki pamięci są trudno wykrywalne we wczesnych stadiach życia oprogramowania. Niewielkie testy mogą nie zaangażować wystarczająco dużej ilości pamięci, by można było uzyskać zauważalne rezultaty. W rozdziale 7. pokazano, w jaki sposób wyposażyć program w monitorowanie wykorzy- stania pamięci na stercie i jak dzięki temu wykrywarć ewentualne wycieki pamięci. Ten potencjalny wyciek pamięci może zostać wyeliminowany poprzez zadeklarowanie de- struktora w klasie bazowej jako funkcji wirtualnej, choć jest to metoda doraźnego łatania dziury, która nie rozwiązuje rzeczywistego strukturalnego problemu występującego w tym kodzie źródło- wym. W przypadku takich klas można znaleźć lepsze rozwiązania tego problemu. Do zagadnienia wirtualnych destruktorów w klasach bazowych wrócimyr w rozdziałach 4. i 9. Istnieją dwa środki zapobiegawcze. Każdy z tych środków pozwala na jednoczesne „wyle- czenie” i problemu luki w enkapsulacji, i problemu wycieku pamięci. Pierwsze lekarstwo polega na tym, by 5VCEM+PFGZ stała się prywatną klasą bazową. Dziedziczenie z użyciem specyfikatora do- stępu RTKXCVG zapobiega przenoszeniu (propagacji) publicznego interfejsu klasy bazowej na pu- bliczne interfejsy klas pochodnych. W ten sposób wyklucza także tę możliwość, by wskaźnik do klasy bazowej lub referencja do klasy bazowej stały się referencjami (wskazaniami) do obiektów klas pochodnych. Jeśli 5VCEM+PFGZ stanie się prywatną klasą bazową (innymi słowy, jeśli dziedzi- czenie będzie następować z użyciem specyfikatora RTKXCVG), próba użycia funkcji takiej jak 8KQ NCVG po stronie kodu klienta spowoduje wygenerowanie carłej serii błędów kompilacji. ENCUU JCT5VCEMRTKXCVG5VCEM+PFGZ]  _ ENCUU+PV5VCEMRTKXCVG5VCEM+PFGZ]  _ XQKF8KQNCVG ] INTERFEJS A IMPLEMENTACJA 67 +PV5VCEMU U5VCEM+PFGZRWUJ MQOWPKMCVQDđúFKG 5VCEM+PFGZ RUMQOWPKMCVQDđúFKG R RWUJ  5VCEM+PFGZTUMQOWPKMCVQDđúFKG TRWUJ  _ Próba odwołania się do prywatnych pól klasy bazowej jest formalnie nielegalna, podobnie jak próba wskazania obiektu klasy pochodnej za pomocą wskaźnika do klasy bazowej lub referencji do klasy bazowej. Jednocześnie problem z destruktorem został także skorygowany. Skoro wskaźnik do klasy bazowej formalnie nie może wskazywać obiektu klasy pochodnej, to niepożądany efekt działania operatora FGNGVG przy zastosowaniu takiego wskaźnika, jak opisano wyżej został wyeli- minowany formalnie, „z definicji”. Interfejs a implementacja Dlaczego i czy w ogóle musimy tu używać dziedziczenia? Jeśli przeanalizujemy dokładniej relacje dziedziczenia, przekonamy się, że to dziedziczenie może zostać całkowicie wyeliminowane. Dru- gim, alternatywnym rozwiązaniem może być zastosowanie obiektu, jako elementu klasy, zamiast dziedziczenia. Jak opisano to w rozdziale 2., klasa w C++ składa się z dwóch głównych części: publicznego interfejsu, który charakteryzuje zachowanie się obiektów danej klasy oraz z prywatnej implemen- tacji mechanizmów realizujących to zachowanie. Większość przypadków dziedziczenia to dzie- dziczenie po publicznej klasie bazowej (innymi słowy, rz specyfikatorem RWDNKE). Klasa pochodna dziedziczy wtedy i interfejs, i implementację po klasie bazowej. Możliwe jest jednak również bar- dziej selektywne (wybiórcze) dziedziczenie, w którym klasa pochodna dziedziczy albo wyłącznie interfejs, albo wyłącznie implementację. Po prywatnej klasie bazowej (inaczej: przy dziedziczeniu z specyfikatorem RTKXCVG) klasa pochodna dziedziczy całą (prywatną) implementację, ale równo- cześnie nie dziedziczy ani jednego elementu publicznego interfejsu klasy bazowej. Po publicznej, abstrakcyjnej klasie bazowej klasa pochodna dziedziczy cały interfejs, ale równocześnie odziedzi- czona implementacja może okazać się niekompletna lurb niespójna. Każde zastosowanie dziedziczenia powinno zostać poddane uważnej ocenie. Czy dziedziczo- ny jest interfejs, implementacja, czy i jedno, i drugie? W tym programie klasy pochodne JCT5VCEM oraz +PV5VCEM dziedziczą tylko implementację. Interfejs klasy pochodnej JCT5VCEM jest podobny, lecz różni się jednak od interfejsu klasy pochodnej +PV5VCEM. Metody należące do obu tych klas pochodnych mają takie same nazwy, ale jedne operują na wartościach typu EJCT, drugie na warto- ściach typu KPV. Klasy pochodne JCT5VCEM oraz +PV5VCEM nie w mają żadnym stopniu wspólnego interfejsu. Kod klienta nie może traktować obiektów tych klas w sposób jednakowy ani zamiennie. Choć jednak pojęcia abstrakcyjne reprezentowane poprzez klasy +PV5VCEM oraz JCT5VCEM stanowią specjalizację w stosunku do pojęć abstrakcyjnych odnoszących się do ogólnego pojęcia stosu, nie stanowią one specjalizacji w stosunku do abstrakcyjnego pojęcia „mechanizmu indeksacji” defi- niowanego przez ich klasę bazową 5VCEM+PFGZ. Z punktu widzenia obiektów klienta każda spośród tych trzech klas: +PV5VCEM, JCT5VCEM, 5VCEM+PFGZ oferuje odrębne usługi. Klasa 5VCEM+PFGZ oferuje obsługę mechanizmu indeksacji, klasa +PV5VCEM oferuje zarządzanie stosem liczb całkowitych, a klasa JCT5VCEM umożliwia obsługę stosu znakowego. Relacja specyfikacji — uogólnienia, którą można by słownie wyrazić jako „A jest szczególnym przypadkiem B” (ang. „is a kind of”) nie ma w odniesieniu 68 3. ZBĘDNE DZIEDZICZENIE do tych klas sensu z punktu widzenia kodu klienta (użytkownika tych klas). Dlatego w tym przypadku dziedziczenia zastosowanie dziedziczenia publicznego interfejsu nie jest rozwiąza- niem właściwym. Relacja pomiędzy klasą pochodną a jej prywatną klasą bazową przypomina relację „klasa klienta-klasa serwera”. Jeśli klasa bazowa 5VCEM+PFGZ będzie prywatną klasą bazową, klasy po- chodne +PV5VCEM oraz JCT5VCEM odziedziczą jej prywatną implementację, w celu wykorzystania jej mechanizmu obsługi indeksacji. W ten sposób klasy pochodne staną się „klientami”, a klasa bazowa będzie ich „serwerem”. Alternatywnym sposobem wyrażenia w C++ takiej relacji „klient- serwer”, bez stosowania dziedziczenia, jest zagnieżdżenie prywatnej części (implementacji), czyli obiektu klasy 5VCEM+PFGZ wewnątrz każdego obiektu klas +PV5VCEM oraz JCT5VCEM. Zamiast pozostawać klasą bazową, 5VCEM+PFGZ może stać się obiektem składowym wewnątrz JCT5VCEM oraz +PV5VCEM. To zmieni strukturę programu w bardzo niewielkim stopniu — pokazano to na listingu 3.3. Ta sama funkcjonalność w obu przypadkach jest realizowana poprzez obiekt takiego samego typu (klasy). Różnica polega na tym, że w tej wersji serwerem jest wewnętrzny obiekt na- leżący do klasy, a nie prywatna część klasy bazowej. Zatem taki serwer jest identyfikowany poprzez nazwę (identyfikator) składowej klasy, a nie poprzez nazwę klasy. W tym konkretnym przypadku wybór jednego z tych rozwiązań: albo prywatnej klasy bazowej, albo prywatnego składowego obiektu wewnątrz klasy jest zdecydowanie kwestią gustu. Te dwie konstrukcje są z funkcjonalnego punktu widzenia równoważne. Mimo to, rozwiązanie stosujące obiekt składowy może być prefe- rowane w celu uproszczenia kodu, ponieważ składnia i semantyka stosowania składowych obiek- tów jest banalnie prosta w porównaniu z semantyką strosowaną podczas dziedziczenia. ► Wykorzystajmy dziedziczenie do przekazania implementacji klasy bazowej, a nie jej interfejsu; w takich przypadkach stosujmy prywatne klasy bazowe albo (lepiej) obiekty składowe klas. LISTING 3.3. Obiekt jako składowa klasy zastępuje dziedziczenie KPENWFGCUUGTVJ KPENWFGUVTKPIJ ENCUU5VCEM+PFGZ] KPVVQR KPVUKG RWDNKE 5VCEM+PFGZ KPVU  KPVRWUJ  KPVRQR  _ 5VCEM+PFGZ5VCEM+PFGZ KPVU ] UKGU VQR _ KPV5VCEM+PFGZRWUJ ] 69 INTERFEJS A IMPLEMENTACJA CUUGTV VQRUKG  TGVWTPVQR  _ KPV5VCEM+PFGZRQR ] CUUGTV VQR   TGVWTPVQR _ EQPUVKPVFGHCWNV5VCEM ENCUU JCT5VCEM] 5VCEM+PFGZKPFGZ EJCT FCVC RWDNKE  JCT5VCEM   JCT5VCEM KPVUKG   JCT5VCEM KPVUKGEJCT KPKV  ` JCT5VCEM  XQKFRWUJ EJCT  EJCTRQR  _ JCT5VCEM JCT5VCEM KPFGZ FGHCWNV5VCEM ] FCVCPGYEJCT=FGHCWNV5VCEM? _ JCT5VCEM JCT5VCEM KPVUKG KPFGZ UKG ] FCVCPGYEJCT=UKG? _ JCT5VCEM JCT5VCEM KPVUKGEJCT KPKV KPFGZ UKG ] FCVCPGYEJCT=UKG? HQT KPVKKUVTNGP KPKV  K FCVC=KPFGZRWUJ ?KPKV=K? _ JCT5VCEM` JCT5VCEM ] FGNGVG=?FCVC _ XQKF JCT5VCEMRWUJ EJCTF ] FCVC=KPFGZRWUJ ?F _ 70 3. ZBĘDNE DZIEDZICZENIE EJCT JCT5VCEMRQR ] TGVWTPFCVC=KPFGZRQR ? _ ENCUU+PV5VCEM] 5VCEM+PFGZKPFGZ KPV FCVC RWDNKE +PV5VCEM MQPUVTWMVQTFQO[ħNP[ +PV5VCEM KPVUKG  `+PV5VCEM  XQKFRWUJ KPV  KPVRQR  _ +PV5VCEM+PV5VCEM KPFGZ FGHCWNV5VCEM ] FCVCPGYKPV=FGHCWNV5VCEM? _ +PV5VCEM+PV5VCEM KPVUKG KPFGZ UKG ] FCVCPGYKPV=UKG? _ +PV5VCEM`+PV5VCEM ] FGNGVG=?FCVC _ XQKF+PV5VCEMRWUJ KPVF ] FCVC=KPFGZRWUJ ?F _ KPV+PV5VCEMRQR ] TGVWTPFCVC=KPFGZRQR ? _ Przeciążanie funkcji w porównaniu ze stosowaniem domyślnych argumentów Zanim przejdziemy do ostatniego przykładu, zwróćmy uwagę na podobieństwa w poddanych prze- ciążeniu funkcji (ang. overloading) konstruktorach w obydwu klasach JCT5VCEM oraz +PV5VCEM. Podobnie jak w przypadku przeciążonego konstruktora klasy UVTKPI, opisanym w rozdziale 2., ZASTOSOWANIE SZABLONÓW 71 zastosowanie domyślnych wartości argumentów powinno było zastąpić stosowanie dwóch kon- struktorów, czyli przeciążanie funkcji w podobny sposób, jak pokazano to wobec klasy JCT5VCEM na listingu 3.4. Dla każdej spośród tych klas wiele konstruktorów sprowadza się do jednego (bardziej złożonego) konstruktora, co upraszcza obsługę takiego kodu. Zastąpienie przeciążania funkcji użyciem domyślnych wartości argumentów nie zawsze jest takie proste, niemniej jednak — po- winno zawsze zostać rozważone, jako rozwiązanie alterrnatywne. Przypomnijmy stosowną regułę: ► Rozważ użycie domyślnych wartości argumentów funkcji jako rozwiązanie alter- natywne wobec przeciążania funkcji. Na koniec, zwróćmy jeszcze uwagę, że konstruktor JCT5VCEM JCT5VCEM na listingu 3.4 wywołuje funkcję biblioteczną UVTNGP podczas każdego cyklu iteracyjnego pętli programowej. Jeśli okaże się, że to ten właśnie konstruktor tworzy „wąskie gardło”, powodując znaczące pogor- szenie prędkości działania programu, usunięcie tego błrędu powinno okazać się łatwe. LISTING 3.4. Zastosowanie domyślnych wartości argumentów pozwala zastąpi1ć przeciążanie funkcji ENCUU JCT5VCEM] 5VCEM+PFGZKPFGZ EJCT FCVC RWDNKE  JCT5VCEM KPVUKGFGHCWNV5VCEMEJCT KPKV  ` JCT5VCEM  XQKFRWUJ EJCT  EJCTRQR  _ JCT5VCEM JCT5VCEM KPVUKGEJCT KPKV KPFGZ UKG ] FCVCPGYEJCT=UKG? HQT KPVKKUVTNGP KPKV  K FCVC=KPFGZRWUJ ?KPKV=K? _ Zastosowanie szablonów Wspólne własności klas +PV5VCEM oraz JCT5VCEM mogą zostać wyrażone w sposób zupełnie inny przy użyciu szablonów w C++ (ang. templates), nazywanych także typami parametryzowanymi. Odpowiedni szablon klasy (ang. class template) dla klasy reprezentującej stos został pokazany na listingu 3.5. Szablon klasy 5VCEM definiuje rodzinę klas. Gdy szablon klasy 5VCEM zostaje użyty do zade- klarowania obiektu, w deklaracji nazwa typu 6 musi zostać zastąpiony konkretnym typem danych. Na przykład, stos przeznaczony na elementy typu EJCT: 5VCEMEJCT UVCEM1H JCT   72 3. ZBĘDNE DZIEDZICZENIE LISTING 3.5. Stos w postaci szablonu klasy Stack EQPUVKPVFGHCWNV5VCEM VGORNCVGENCUU6 ENCUU5VCEM] KPVUKG KPVVQR 6 FCVC RWDNKE 5VCEM KPVUKGFGHCWNV5VCEM  `5VCEM  XQKFRWUJ 6  6RQR XQKF  _ VGORNCVGENCUU6 5VCEM6 5VCEM KPVU ] UKGU VQR FCVCPGY6=UKG? _ VGORNCVGENCUU6 5VCEM6 `5VCEM ] FGNGVG=?FCVC _ VGORNCVGENCUU6 5VCEM6 RWUJ 6F ] CUUGTV VQRUKG  FCVC=VQR ?F _ VGORNCVGENCUU6 65VCEM6 RQR ] CUUGTV VQR   TGVWTPFCVC=VQR? _ Powyższa deklaracja powoduje utworzenie obiektu o nawie UVCEM1H JCT będącego stosem do przechowywania maksymalnie 10 elementów typu EJCT, natomiast: 5VCEMKPV UVCEM1H+PV   PODSUMOWANIE 73 spowoduje utworzenie obiektu o nazwie UVCEM1H+PV, czyli stosu do przechowywania maksymalnie 20 wartości typu KPV. Typ argumentu metody odkładającej na stos RWUJ oraz typ wartości zwra- canej metody zdejmującej ze stosu RQR są także określone za pomocą nazwy typu 6. Podstawowym i zasadniczym motywem, który spowodował dodanie szablonów do C++ była zapewniana przez szablony obsługa ogólnych klas — kolekcji. W ten sposób można utworzyć nie tylko stos liczb całkowitych i znaków, lecz równie dobrze można zbudować stosy złożone z liczb zmiennoprzecinkowych, ze wskaźników do znaków, itd.r Zachowanie się obiektów utworzonych na podstawie takiego szablonu klas w jeden, dość subtelny sposób różni się od tych obiektów, które były uprzednio tworzone na podstawie oryginal- nych definicji klas +PV5VCEM oraz JCT5VCEM. Oryginalny konstruktor z klasy JCT5VCEM mógłby obierać drugi argument, który mógłby określać łańcuch znaków przeznaczony do umieszczenia (RWUJ) na stosie. Jednakże w klasie +PV5VCEM nie ma żadnego konstruktora, który mógłby pobierać taki (analogiczny) argument. Jeśli zastosujemy szablon do opisu obu tych klas jednocześnie, nie ma żadnego sposobu, by wyrazić takie zróżnicowanie. Podsumowanie Pierwszą i najważniejszą zmianą, której dokonaliśmy w pierwszym programie było zmodyfikowa- nie abstrakcyjnej konstrukcji zdefiniowanej poprzez klasę 5VCEM. Oryginalny interfejs był zdefi- niowany w kategoriach wskaźników do elementów tablicy użytkownika. Abstrakcyjna konstrukcja stosu została uproszczona poprzez wyrażenie interfejsu za pomocą innej kategorii — liczby cał- kowitej reprezentującej indeks stosu. Powstała w rezultacie tych modyfikacji nowa klasa, nazwana 5VCEM+PFGZ, w sposób bardziej spójny i klarowny obejmowała ogólne własności stosu i jednocze- śnie pozwoliła zignorować nie mające istotnego znaczenia szczegóły techniczne reprezentacji. Ogólnie, im prostsze jest pojęcie abstrakcyjne, tym, po prostu, lepiej, dopóki tylko taka abstrak- cyjna reprezentacja pozostaje wystarczająca. W przypadku tego programu, prostsza abstrakcja okazała się bardziej bezpieczna, ponieważ to uproszczenie pozwoliło wyeliminować liczne wymu- szone konwersje typu. Stała się jednocześnie bardziej efektywna, ponieważ wyeliminowana zo- stała z programu tablica zawierająca wskaźniki do danyrch. Drugą fundamentalną zmianą w tym programie, która pozwoliła na jego skorygowanie i wy- eliminowanie luki w enkapsulacji stosu (wywołanej poprzez zastosowanie publicznego dziedzi- czenia, podczas gdy klasy pochodne potrzebowały tylko części implementacyjnej po klasie bazo- wej), było zastąpienie dziedziczenia. Prywatny, obiekt składowy klasy 5VCEM+PFGZ okazał się dla klas JCT5VCEM oraz +PV5VCEM rozwiązaniem prostszym, a umożliwiającym im wykorzystanie funkcjonalności klasy 5VCEM+PFGZ. Bibliografia Model „klient-serwer” został opisany w książce [2], Gorlena i in. oraz [3], Wirfsa-Brocka i in. Termin „klient” jest również stosowany w odniesieniu do programisty piszącego „kod klienta” (tj. kod użytkowy, posługujący się obiektami danych klas). Termin „serwer” (oznaczający generalnie „stronę usługową”, klasę, funkcję, itp.) bywa czasem zastępowany określeniem „usługodawca” (ang. supplier). 74 3. ZBĘDNE DZIEDZICZENIE [1] Ellis M.A. Stroustrup B., The Annotated C++ Reference Manual. Reading, MA: Addison- -Wesley, 1990. [2] Gorlen K. E., Orlow S. M., Plexico P. S., Data Abstraction and Object-Oriented Programming in C++. Chichester, England: Wiley, 1990. [3] Wirfs-Brock R., Wilkerson B., Wiener L., Designing Object-Oriented Software. Englewoods Cliffs, NJ: Prentice-Hall, 1990. Ćwiczenie 3.1. Klasa 5VCEM+PFGZ może być albo prywatną klasą bazową, albo posłużyć do utworzenia prywatnego obiektu składowego wewnątrz klas +PV5VCEM oraz JCT5VCEM. Skonstruuj taką sytuację, w której nie można by mieć takiego wyboru. Najpierw utwórz klasę korzystającą z usług innej klasy, która to musi zostać prywatną klasą bazową. Następnie utwórz taką klasę, w której potrzebna funkcjonalność musi zostać zaimplementowana w postaci pry- watnego obiektu składowego wewnątrz tej klasy.
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

C++. Styl programowania
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


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