24
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++. 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¿noci 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 nieprzemylane 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ójnoci, 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 podejcia do przedstawionych zagadnieñ programistycznych. Te w³asne przemylenia 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 powiê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 wielkoci kodu. Konkluzjê ksi¹¿ki stanowi rozdzia³ powiê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êtnoci programistycznych.

C++. Styl programowania

Embed Size (px)

DESCRIPTION

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: * 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.

Citation preview

Page 1: C++. Styl programowania

Wydawnictwo Helion

ul. Chopina 6

44-100 Gliwice

tel. (32)230-98-63

e-mail: [email protected]

PRZYK£ADOWY ROZDZIA£PRZYK£ADOWY ROZDZIA£

IDZ DOIDZ DO

ZAMÓW DRUKOWANY KATALOGZAMÓW DRUKOWANY KATALOG

KATALOG KSI¥¯EKKATALOG KSI¥¯EK

TWÓJ KOSZYKTWÓJ KOSZYK

CENNIK I INFORMACJECENNIK I INFORMACJE

ZAMÓW INFORMACJEO NOWOCIACH

ZAMÓW INFORMACJEO NOWOCIACH

ZAMÓW CENNIKZAMÓW CENNIK

CZYTELNIACZYTELNIA

FRAGMENTY KSI¥¯EK ONLINEFRAGMENTY KSI¥¯EK ONLINE

SPIS TRECISPIS TRECI

DODAJ DO KOSZYKADODAJ DO KOSZYKA

KATALOG ONLINEKATALOG ONLINE

C++. StylprogramowaniaAutor: 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¿noci 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 nieprzemylane 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ójnoci, 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

podejcia do przedstawionych zagadnieñ programistycznych. Te w³asne przemylenia

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 powiê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 wielkoci kodu. Konkluzjê ksi¹¿ki

stanowi rozdzia³ powiê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êtnoci programistycznych.

Page 2: C++. Styl programowania

Spis treści

Przedmowa...............................................................................................................7

Wprowadzenie .........................................................................................................9

1.

Abstrakcja ..............................................................................................................15

Przykład techniki programowania: ceny komputerów...................................................................................... 15Poszukiwanie wspólnego pojęcia abstrakcyjnego............................................................................................. 19Różnice pomiędzy klasami................................................................................................................................ 22Powtórnie wprowadzamy do programu dziedziczenie...................................................................................... 26Wyeliminowanie typów wyliczeniowych ......................................................................................................... 27Podsumowanie .................................................................................................................................................. 30Bibliografia ....................................................................................................................................................... 31Ćwiczenie .......................................................................................................................................................... 31

2.

Spójność kodu........................................................................................................35

Przykład techniki programowania: klasa string ................................................................................................ 36Precyzyjnie zdefiniowany stan obiektu............................................................................................................. 37Spójność fizycznego stanu obiektu ................................................................................................................... 38Niezmienne warunki, czyli inwariancja klas..................................................................................................... 39Konsekwentne stosowanie dynamicznego zarządzania pamięcią..................................................................... 41Zwolnienie dynamicznie przyporządkowanej pamięci ..................................................................................... 43Przykład techniki programowania: drugie podejście do tego samego problemu.............................................. 44Podsumowanie .................................................................................................................................................. 50Bibliografia ....................................................................................................................................................... 51Ćwiczenia .......................................................................................................................................................... 51

3.

Zbędne dziedziczenie.............................................................................................55

Przykład techniki programowania: stos ............................................................................................................ 55Reguły widoczności nazw w procesie dziedziczenia........................................................................................ 59Relacje tworzone poprzez dziedziczenie........................................................................................................... 60

Page 3: C++. Styl programowania

4 SPIS TREŚCI

Enkapsulacja...................................................................................................................................................... 65Interfejs a implementacja .................................................................................................................................. 67Zastosowanie szablonów................................................................................................................................... 71Podsumowanie .................................................................................................................................................. 73Bibliografia ....................................................................................................................................................... 73Ćwiczenie .......................................................................................................................................................... 74

4.

Funkcje wirtualne ..................................................................................................75

Przykład techniki programowania: problem pojazdów i garaży....................................................................... 75Spójność kodu ................................................................................................................................................... 79Destruktory klas bazowych ............................................................................................................................... 81Dziedziczenie .................................................................................................................................................... 82Sprzężenie ......................................................................................................................................................... 85Podsumowanie .................................................................................................................................................. 91Bibliografia ....................................................................................................................................................... 92Ćwiczenie .......................................................................................................................................................... 92

5.

Przeciążanie operatorów ........................................................................................93

Przeciążanie operatorów — podstawy .............................................................................................................. 93Przykład techniki programowania: tablica plików FileArray ........................................................................... 98Dziedziczenie implementacji .......................................................................................................................... 105Programistyczny dylemat — jak lepiej: przeciążone operatory czy metody .................................................. 110Podsumowanie ................................................................................................................................................ 112Bibliografia ..................................................................................................................................................... 112Ćwiczenia ........................................................................................................................................................ 112

6.

Klasy otaczające (ang. wrappers) ........................................................................113

Biblioteka C .................................................................................................................................................... 113Przykład techniki programowania: klasa otaczająca dla struktury dirent w C++........................................... 114Wiele obiektów klasy Directory...................................................................................................................... 116Gdy zawodzi konstruktor ................................................................................................................................ 119Publiczny dostęp do stanu obiektu .................................................................................................................. 121Dodatkowy argument służący obsłudze błędów ............................................................................................. 123Podsumowanie ................................................................................................................................................ 127Bibliografia ..................................................................................................................................................... 128Ćwiczenie ........................................................................................................................................................ 128

7.

Efektywność.........................................................................................................129

Przykład techniki programowania: klasa BigInt ............................................................................................. 130Egzaminujemy klasę BigInt ............................................................................................................................ 136Długość dynamicznych łańcuchów znaków ................................................................................................... 138Liczba dynamicznie zapamiętywanych łańcuchów znaków........................................................................... 140Kod klienta ...................................................................................................................................................... 144Modyfikujemy klasę BigInt ............................................................................................................................ 145

Page 4: C++. Styl programowania

SPIS TREŚCI 5

Podsumowanie ................................................................................................................................................ 151Bibliografia ..................................................................................................................................................... 152Ćwiczenia ........................................................................................................................................................ 152

8.

Studium praktyczne .............................................................................................153

Przykład techniki programowania: Finite State Machine, czyli automat stanów skończonych ..................... 153Zainicjowanie .................................................................................................................................................. 158Sprzężenia ....................................................................................................................................................... 165Spójność .......................................................................................................................................................... 169Moduły kontra abstrakcyjne typy danych ....................................................................................................... 172Wartość kontra zachowanie ............................................................................................................................ 175Uogólnienie ..................................................................................................................................................... 180Bibliografia ..................................................................................................................................................... 184Ćwiczenia ........................................................................................................................................................ 185

9.

Dziedziczenie wielokrotne...................................................................................187

Niejednoznaczności w mechanizmie wielokrotnego dziedziczenia................................................................ 187Skierowane niecykliczne grafy hierarchii dziedziczenia ................................................................................ 189Eksplorujemy wirtualne klasy bazowe............................................................................................................ 193Przykład techniki programowania: klasa Monitor .......................................................................................... 200Przykład techniki programowania: wirtualna klasa bazowa........................................................................... 204Protokół powiadomień zwrotnych i przydatne wielokrotne dziedziczenie..................................................... 210Podsumowanie ................................................................................................................................................ 213Bibliografia ..................................................................................................................................................... 213Ćwiczenia ........................................................................................................................................................ 213

10.

Zbiorcza lista reguł ..............................................................................................215

Rozdział 1.: „Abstrakcja”................................................................................................................................ 215Rozdział 2.: „Spójność kodu” ......................................................................................................................... 215Rozdział 3.: „Zbędne dziedziczenie” .............................................................................................................. 216Rozdział 4.: „Funkcje wirtualne” .................................................................................................................... 216Rozdział 5.: „Przeciążanie operatorów”.......................................................................................................... 216Rozdział 6.: „Klasy otaczające (ang. wrappers)”............................................................................................ 216Rozdział 7.: „Efektywność” ............................................................................................................................ 217Rozdział 8.: „Studium praktyczne”................................................................................................................. 217Rozdział 9.: „Dziedziczenie wielokrotne” ...................................................................................................... 217

Skorowidz ............................................................................................................219

Page 5: C++. Styl programowania

3Zbę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ą klasyw 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 spowoduje w efekcie znaczącą modyfikację kodu.

Przykład techniki programowania: stosNa listingu 3.1 pokazano program, w którym zostały zdefiniowane klasy (dosł. stos prze-znaczony na znaki) oraz (dosł. stos przeznaczony na liczby typu ). Przeanalizujmy i oceń-my te klasy. Czy widać tu uogólnienie pojęć abstrakcyjnych? Czy dziedziczenie działa w sposóbpoprawny? Czy dziedziczenie zostało wykorzystane we właściwy sposób?

LISTING 3.1.

Oryginalna wersja klas Stack, CharStack oraz IntStack

!"#$ $%&&% '()* " $+, $,)*( $-)* $,%&)*!. (/.%&)*!.,!,.0

Page 6: C++. Styl programowania

56 3. ZBĘDNE DZIEDZICZENIE

"")*+,%1%&213140

""-)* 23% !#5,!6 #'(0

%&"")*)*%27730

%&"")*)4*%28830

+ 19:;!$< $!

" &$$'$ ")*!$< $)*)=&*-)*

%)*)*0

"")*")+ *12+ 3+)14+ 77*%231>230

"")*")*123+)1477*%231>230

Page 7: C++. Styl programowania

PRZYKŁAD TECHNIKI PROGRAMOWANIA: STOS 57

"")=&*")*123+)1477*%231>23+)14 )*77*&))&*"")**1230

""-)* 230

%"")*&))&*"")**10

"")*&))&*"")**0

" &$$'$ ")*!$< $)*-)*

%)*)*0

"")*")+ *12+ 3+)14+ 77*%231>230

"")*")*123+)1477*%231>230

""-)*

Page 8: C++. Styl programowania

58 3. ZBĘDNE DZIEDZICZENIE

230

%"")*&))&*"")**10

"")*&))&*"")**0

Natychmiastowa, odruchowa reakcja wielu Czytelników na samo hasło oraz 8 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 proponujestandard ANSI C++ (patrz: Ellis i Stroustrup). Ignorujemy tę możliwość i skoncentrujemy się naprogramie w takiej postaci, w jakiej został napisany. A to z dwóch powodów. Po pierwsze, ważnejest zrozumienie, przy wykorzystaniu najbardziej podstawowych konstrukcji języka C++, w jakisposób powstała struktura tego programu, zanim jeszcze zaczniemy rozważać, w jaki sposób możnaby 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 szerokorozpowszechnione i dostępne. Co więcej, należało się liczyć z tym, że jeszcze przez pewien czasszablony nie zostaną poprawnie zaadaptowane do języka C++. Przedwczesne byłoby rozważanietechnik programowania w odniesieniu do typów parametryzowanych przed nagromadzeniem sto-sownego praktycznego doświadczenia, co pozwoliłoby na poradnictwo, jak właściwie i efektywniestosować te techniki. Wersja kodu napisana z zastosowaniem szablonów została podana na końcutego rozdziału.

Istota tej konstrukcji kodu sprowadza się do tego, że obydwie klasy: oraz reprezentują stos (pierwsza dla znaków, druga dla liczb całkowitych), zatem obie mają wspólnąklasę bazową (po prostu „Stos”, bez skonkretyzowania szczegółów). Taka hierarchia dzie-dziczenia została schematycznie przedstawiona na rysunku 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 niejest niezbędne. Co więcej, okazuje się mylące i wręcz powinno zostać wyeliminowane. W istocie,zastosowanie dziedziczenia z specyfikatorem dostępu " w tym programie tworzy niebez-pieczną lukę w enkapsulacji takich stosów.

Page 9: C++. Styl programowania

REGUŁY WIDOCZNOŚCI NAZW W PROCESIE DZIEDZICZENIA 59

Reguły widoczności nazw w procesie dziedziczeniaDziedziczenie może następować z użyciem specyfikatorów dostępu , %, .Sposób dziedziczenia decyduje o widoczności i dostępności nazw (ang. inheritance scope rules).Publiczny interfejs klasy bazowej jest następujący:

")*-)*%&)*%&)*0

Patrząc na klasy pochodne, widzimy tam metody o tych samych nazwach ()* oraz)*), co w klasie bazowej. Zwróćmy uwagę, że metody "")* oraz "")* niesą 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 "")* jest bezargumentowa, podczas gdy metoda "")* pobierajeden argument typu . W przypadku takich funkcji reguły widoczności nazw C++ mówią, żemetoda 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 dostrzec na następującym przykładzie:

? "%+)+ *%)+ *0 @%" ? "%+)*0

!)*)@%+)9A*$ "@%""+)9A*)9A*$ "?"")9A*

Wyraźnie widać, że w tym przypadku (przy dziedziczeniu) funkcja — metoda o nazwie +)* niejest przeciążona (ang. not overloaded). Klasa pochodna definiuje funkcję o nazwie +)*, a zatemwyrażenie +)9A* musi jednoznacznie wywołać metodę +)* zdefiniowaną w klasie pochodnej,a nie w klasie bazowej. Natomiast wyrażenie )9A* wywołuje metodę z klasy bazowej, ponieważw klasie pochodnej nie została zdefiniowana własna wersja metody o nazwie )*. Gdy kompilatorC++ poszukuje w celu wywołania metody )*, najpierw przeszukiwana jest klasa pochodna, jeśliw klasie pochodnej nie zostanie odszukana żadna „pasująca” metoda, w drugiej kolejności poszu-kiwanie nastąpi w klasie bazowej. Zatem wywołanie metody )* wobec obiektu klasy pochodnejspowoduje w istocie zastosowanie wersji ?"")*z klasy bazowej, ale wywołanie metody +)*będzie oznaczać @%""+)*, a nie ?""+)*, która została przesłonięta i stała się w ten sposóbniewidoczna.

Page 10: C++. Styl programowania

60 3. ZBĘDNE DZIEDZICZENIE

Publiczny interfejs klasy jest następujący:

")*)*-)*%)*)*0

Zgodnie z zasadami widoczności (dostępności) nazw opisanymi powyżej, kod klienta możemanipulować obiektami klas lub przy użyciu wywołań ich własnych metod)* oraz )*, a nie poprzez metody odziedziczone po klasie bazowej . Klasy pochodne oraz dziedziczą publiczny interfejs po klasie bazowej , ale odziedziczonemetody przesłaniają własnymi wersjami tych funkcji. Powrócimy jeszcze do tego zagadnienia w dal-szej części tego rozdziału.

Relacje tworzone poprzez dziedziczeniePrzyjrzyjmy się na razie bliżej relacjom tworzonym poprzez dziedziczenie. Klasa bazowa 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 %. Tablica ta jest typu chronionego i składa się ze wskaźników do typu %. Klasa zarządza także prywatnym polem danych (wierzchołek stosu) pozwalającą na obsługętablicy w odpowiedzi na wywołania metod )* oraz )* z klas pochodnych. Klasy pochodnetworzą własne tablice — wektory wskazywane poprzez wskaźniki dla wartości przechowy-wanych na stosie i inicjują te wektory tak, że dla wszystkich wartości indeksu tablicy (oczywi-ście, w granicach wektora) element %23 jest wskaźnikiem do elementu 23. Wskaźniki typu% zwracane przez metody "")* oraz "")* mówią klasie pochodnej, na któ-rym elemencie tablicy mają być wykonywane odpowiednio operacje (odłóż na stos) oraz (zdejmij ze stosu).

Struktura danych została przedstawiona na rysunku 3.2. Zwróćmy uwagę na spójność układuwskaźników. Taka struktura danych zawiera, niestety, niewiele informacji. Konstruktory klas po-chodnych zawierają i wykonują instrukcję:

%231>23

dla każdego z elementów wskazywanych w tablicy 23, aby poprawnie zainicjować wskaźnikiwchodzące w skład tablicy %23. Po takim zadziałaniu konstruktora i zainicjowaniu, te wskaźnikinigdy nie ulegają zmianie. Klasa bazowa odsyła te wskaźniki z powrotem, by „powiedzieć” klasiepochodnej, gdzie (pod jakim adresem) znajdują się dane, do których można się odwoływać. Coś tujest 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"")*, zobaczymy tę samą sytuację z innej perspektywy.

Page 11: C++. Styl programowania

RELACJE TWORZONE POPRZEZ DZIEDZICZENIE 61

RYSUNEK 3.2.

Relacja wskaźników

z wektora vec

do elementów

tablicy data

"")*&))&*"")**0

Ta wymuszona konwersja powoduje przekształcenie wskaźnika do typu % zwróconegoprzez metodę "")* na wskaźnik do typu w celu odwołania się do elementu tablicy wska-zywanej przez wskaźnik "". 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órysprawia wektor %. Ogólnie rzecz biorąc, logika i kolejność działań jest tu następująca:

• metoda "")* zwraca wskaźnik %23,• wskaźnik %23 wskazuje element tablicy 23,• metoda "")* zwraca 23.

Żadna konwersja typów nie byłaby tu potrzebna, gdyby funkcja "")* zwracała samindeks , bezpośrednio, zamiast wskaźnika %23. Mając wartość indeksu , metoda "")* bezpośrednio odwoływałaby się do elementu tablicy 23, bez żadnej konwersji typu.Metody )* oraz )* należące do klasy bazowej mogłyby zwracać pojedynczą liczbęcałkowitą (indeks), której obiekt klasy pochodnej rzeczywiście potrzebuje, zamiast wskaźnika typu %,który przed użyciem musi zostać poddany konwersji typu. Taka prostsza wersja klasy zostałapokazana na listingu 3.2. Zwróćmy uwagę, że metody )* oraz )* należące do klas pochod-nych oraz w tej wersji już nie stosują wymuszonej konwersji typu. Zauważmytakże, że nazwa została zmieniona na B, co lepiej opisuje to pojęcie abstrakcyjne.

LISTING 3.2.Uproszczona klasa abstrakcji „Indeksacja stosu”

B ,$ $

Page 12: C++. Styl programowania

62 3. ZBĘDNE DZIEDZICZENIE

8. .8! " $+, $,B)*( $-B)* $,)*!. (/.)*!.,!,.0

B""B)*1140

B""-B)*0$

B"")*)*770

B"")*)4*880

+ 19:;!$< $!

" B&$$'$ ")*!$< $)*)=&*-)*

%)*)*0

"")*"B)+ *12+ 30

"")*"B)*1230

Page 13: C++. Styl programowania

RELACJE TWORZONE POPRZEZ DZIEDZICZENIE 63

"")=&*"B)*123+)14 )*77*2B"")*31230

""-)* 230

%"")*2B"")*310

"")*2B"")*30

" B&$$'$ ")*!$< $)*-)*

%)*)*0

"")*"B)+ *12+ 30

"")*"B)*1230

""-)* 230

%"")*2B"")*310

Page 14: C++. Styl programowania

64 3. ZBĘDNE DZIEDZICZENIE

"")*2B"")*30

Zastosowany tu nowy interfejs klasy bazowej B lepiej odpowiada abstrakcyjnemupoję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órychmożna się odwoływać. Informacja o tym, w jaki sposób działa i zachowuje się stos, pozostaje ukrytaprzed klasą bazową . 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ścizostanie 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 ""% jest nadmiarowa (stanowi redundancję), dlatego zniknęła z nowejwersji klasy B. 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 doklasy bazowej: B"")* oraz B"")*. Nie ma żadnego racjonalnego uza-sadnienia, by w klasie bazowej przechowywać wskaźniki do danych znajdujących się w tablicachdeklarowanych 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 ""% gwałtowniezmniejsza ilość pamięci wymaganą do utworzenia stosu. Przy typowej, 32-bitowej architekturze,gdy liczby całkowite typu zajmują po 4 bajty pamięci każda i przy 4-bajtowych wskaźnikach,obiekt klasy 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 stercieprzez obiekt klasy należy podzielić przez współczynnik 5. Zamiast 5 bajtów na każdyznak na stosie przypada teraz tylko 1 bajt.

RYSUNEK 3.3.

Uproszczona

struktura danych

Page 15: C++. Styl programowania

ENKAPSULACJA 65

EnkapsulacjaTaka nowa reprezentacja abstrakcyjnego pojęcia stosu jest prostsza, a sam program jest krótszyi wykorzystuje mniej pamięci. Relacje wprowadzone poprzez mechanizm dziedziczenia pomiędzyklasą bazową B a jej klasami pochodnymi nie zostały jeszcze do końca wyjaśnione. Wcze-śniej w tym rozdziale zasygnalizowano, że metody )* oraz )* należące do klas pochodnychprzesłaniają metody o tych samych nazwach należące do klasy bazowej. Jeśli jednak stosuje siępełne, rozwinięte nazwy funkcji, np.: B"")* i odpowiednio B"")*, te me-tody mogą nadal być wywołane w zakresie widoczności nazw utworzonym przez klasy pochodne,przez obiekty klas lub . W istocie, metody B"")* 8B"")* są właśnie w taki sposób wywoływane w kodzie przez odpowiednie metody należącedo klas pochodnych.

Metody z klasy bazowej, B"")* oraz B"")*, mogą zostać wywoła-ne nawet bez potrzeby sięgania do operatora widoczności "". Jeśli do obiektu klasy pochodnej lub odwołujemy się za pośrednictwem wskaźnika do obiektu klasy bazowejB albo za pośrednictwem referencji do obiektu klasy B, to )* i )* oznaczająwywołania metod należących do klasy bazowej. Problem dostępności metod należących do klasbazowych to bardzo poważne zagadnienie (a w tym przypadku — naruszenie) z punktu widzeniaenkapsulacji stosów. Przy takiej konstrukcji programu możliwe jest zamierzone bądź omyłkowewprowadzenie obiektu „stos” w stan niespójny logicznie poprzez odwołanie z innych części pro-gramu. Poniższa funkcja prezentuje trzy sposoby naruszenia hermetyzacji obiektu klasy .

%C )*B"")* , $,B&1>8)*$ B"")*B>1)*$ B"")*0

Poprzez bezpośrednie wywołanie publicznych składowych klasy bazowej z kodu klienta, funk-cja C )* (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śnieo jeden nowy element, ale skoro w chwili wykonania operacji „push” nie zostaje dostarczona żadnawartość do umieszczenia na stosie, to wartość zwracana przez metodę "")* pozostajenieokreś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 .

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 naszejmodyfikacji kodu w celu wyeliminowania tablicy zawierającej wskaźniki w klasie bazowej. Ta lukaw enkapsulacji występowała już w oryginalnym kodzie wyjś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 zostanie utworzony w spo-sób dynamiczny, a następnie zostanie usunięty operatorem , kasującym wskaźnik do klasybazowej, to zostanie wywołany tylko destruktor klasy bazowej. Pod tym względem destruktory

Page 16: C++. Styl programowania

66 3. ZBĘDNE DZIEDZICZENIE

zachowują się tak samo, jak inne metody. Rodzaj destruktora (tj. jego przynależność do określonejklasy) jest determinowany poprzez typ wskaźnika (typ wartości wskazywanej przez dany wskaźnikużyty w momencie deklaracji).

&1B&D1 D,$ $ B""-B)*

Skoro destruktor klasy pochodnej nie może zostać wywołany, programy (kod klienta) posłu-gujące się klasą lub są potencjalnie zagrożone występowaniem „wyciekówpamięci”. Wyciek pamięci omawiany w rozdziale 2. na przykładzie klasy występował dla-tego, że pominięto pożądany operator w kodzie metody. W tym przypadku „wyciek pamięci”wystąpi nawet wtedy, gdy destruktor w klasie pochodnej będzie całkowicie poprawny. Operator użyty w kodzie destruktora klasy pochodnej może zostać wykonany tylko wtedy, gdy tendestruktor zostanie wywołany. Ten destruktor nie zostanie wywołany, jeśli obiekt klasy pochodnejbędzie usuwany poprzez skasowanie wskaźnika do klasy bazowej (wskaźniki do klas bazowychmogą 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óconado systemu, w miarę działania programu będzie wzrastać ilość zajętej, choć bezużytecznej pamięcina stercie, co może doprowadzić nawet do przepełnienia pamięci przeznaczonej na stertę. Wyciekipamięci są trudno wykrywalne we wczesnych stadiach życia oprogramowania. Niewielkie testymogą nie zaangażować wystarczająco dużej ilości pamięci, by można było uzyskać zauważalnerezultaty. W rozdziale 7. pokazano, w jaki sposób wyposażyć program w monitorowanie wykorzy-stania pamięci na stercie i jak dzięki temu wykrywać 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 zagadnieniawirtualnych destruktorów w klasach bazowych wrócimy 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 natym, by B stała się prywatną klasą bazową. Dziedziczenie z użyciem specyfikatora do-stępu %" 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 doklasy bazowej lub referencja do klasy bazowej stały się referencjami (wskazaniami) do obiektówklas pochodnych. Jeśli B stanie się prywatną klasą bazową (innymi słowy, jeśli dziedzi-czenie będzie następować z użyciem specyfikatora %"), próba użycia funkcji takiej jak C8 )* po stronie kodu klienta spowoduje wygenerowanie całej serii błędów kompilacji.

"%B0 "%B0%C )*

Page 17: C++. Styl programowania

INTERFEJS A IMPLEMENTACJA 67

B"")*! #B&1>! #8)*B>1! #)*0

Próba odwołania się do prywatnych pól klasy bazowej jest formalnie nielegalna, podobnie jakpróba wskazania obiektu klasy pochodnej za pomocą wskaźnika do klasy bazowej lub referencjido klasy bazowej. Jednocześnie problem z destruktorem został także skorygowany. Skoro wskaźnikdo klasy bazowej formalnie nie może wskazywać obiektu klasy pochodnej, to niepożądany efektdziałania operatora przy zastosowaniu takiego wskaźnika, jak opisano wyżej został wyeli-minowany formalnie, „z definicji”.

Interfejs a implementacjaDlaczego i czy w ogóle musimy tu używać dziedziczenia? Jeśli przeanalizujemy dokładniej relacjedziedziczenia, przekonamy się, że to dziedziczenie może zostać całkowicie wyeliminowane. Dru-gim, alternatywnym rozwiązaniem może być zastosowanie obiektu, jako elementu klasy, zamiastdziedziczenia.

Jak opisano to w rozdziale 2., klasa w C++ składa się z dwóch głównych części: publicznegointerfejsu, 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, z specyfikatorem "). Klasa pochodnadziedziczy 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łącznieinterfejs, albo wyłącznie implementację. Po prywatnej klasie bazowej (inaczej: przy dziedziczeniuz specyfikatorem %") 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 lub 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 oraz dziedziczą tylko implementację. Interfejs klasy pochodnej jest podobny,lecz różni się jednak od interfejsu klasy pochodnej . Metody należące do obu tych klaspochodnych mają takie same nazwy, ale jedne operują na wartościach typu , drugie na warto-ściach typu . Klasy pochodne oraz nie w mają żadnym stopniu wspólnegointerfejsu. Kod klienta nie może traktować obiektów tych klas w sposób jednakowy ani zamiennie.Choć jednak pojęcia abstrakcyjne reprezentowane poprzez klasy oraz stanowiąspecjalizację w stosunku do pojęć abstrakcyjnych odnoszących się do ogólnego pojęcia stosu, niestanowią one specjalizacji w stosunku do abstrakcyjnego pojęcia „mechanizmu indeksacji” defi-niowanego przez ich klasę bazową B. Z punktu widzenia obiektów klienta każda spośródtych trzech klas: , , B oferuje odrębne usługi. Klasa B oferujeobsługę mechanizmu indeksacji, klasa oferuje zarządzanie stosem liczb całkowitych, a klasa umożliwia obsługę stosu znakowego. Relacja specyfikacji — uogólnienia, którą można bysłownie wyrazić jako „A jest szczególnym przypadkiem B” (ang. „is a kind of”) nie ma w odniesieniu

Page 18: C++. Styl programowania

68 3. ZBĘDNE DZIEDZICZENIE

do tych klas sensu z punktu widzenia kodu klienta (użytkownika tych klas). Dlatego w tymprzypadku 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ę „klasaklienta-klasa serwera”. Jeśli klasa bazowa B będzie prywatną klasą bazową, klasy po-chodne oraz odziedziczą jej prywatną implementację, w celu wykorzystaniajej mechanizmu obsługi indeksacji. W ten sposób klasy pochodne staną się „klientami”, a klasabazowa 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), czyliobiektu klasy B wewnątrz każdego obiektu klas oraz .

Zamiast pozostawać klasą bazową, B może stać się obiektem składowym wewnątrz oraz . To zmieni strukturę programu w bardzo niewielkim stopniu — pokazano tona listingu 3.3. Ta sama funkcjonalność w obu przypadkach jest realizowana poprzez obiekt takiegosamego 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 poprzeznazwę (identyfikator) składowej klasy, a nie poprzez nazwę klasy. W tym konkretnym przypadkuwybór jednego z tych rozwiązań: albo prywatnej klasy bazowej, albo prywatnego składowegoobiektu wewnątrz klasy jest zdecydowanie kwestią gustu. Te dwie konstrukcje są z funkcjonalnegopunktu 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ą stosowaną podczas dziedziczenia.

Wykorzystajmy dziedziczenie do przekazania implementacji klasy bazowej, a niejej 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

B "B)*)*)*0

B""B)*1140

B"")*

Page 19: C++. Styl programowania

INTERFEJS A IMPLEMENTACJA 69

)*770

B"")*)4*880

+ 19:;

BB& ")*)*)=&*-)*

%)*)*0

"")*"B)+ *12+ 30

"")*"B)*1230

"")=&*"B)*123+)14 )*77*2B)*31230

""-)* 230

%"")*2B)*310

Page 20: C++. Styl programowania

70 3. ZBĘDNE DZIEDZICZENIE

"")*2B)*30

BB& ")*!$< $)*-)*

%)*)*0

"")*"B)+ *12+ 30

"")*"B)*1230

""-)* 230

%"")*2B)*310

"")*2B)*30

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 oraz .Podobnie jak w przypadku przeciążonego konstruktora klasy , opisanym w rozdziale 2.,

Page 21: C++. Styl programowania

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 nalistingu 3.4. Dla każdej spośród tych klas wiele konstruktorów sprowadza się do jednego (bardziejzłożonego) konstruktora, co upraszcza obsługę takiego kodu. Zastąpienie przeciążania funkcjiużyciem domyślnych wartości argumentów nie zawsze jest takie proste, niemniej jednak — po-winno zawsze zostać rozważone, jako rozwiązanie alternatywne. 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 "" na listingu 3.4wywołuje funkcję biblioteczną )* 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łędu powinno okazać się łatwe.

LISTING 3.4.

Zastosowanie domyślnych wartości argumentów pozwala zastąpić przeciążanie funkcji

BB& ")1+ =&1*-)*

%)*)*0

"")=&*"B)*123+)14 )*77*2B)*31230

Zastosowanie szablonówWspólne własności klas oraz mogą zostać wyrażone w sposób zupełnie innyprzy 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 nalistingu 3.5.

Szablon klasy definiuje rodzinę klas. Gdy szablon klasy zostaje użyty do zade-klarowania obiektu, w deklaracji nazwa typu E musi zostać zastąpiony konkretnym typem danych.Na przykład, stos przeznaczony na elementy typu :

F+)94*

Page 22: C++. Styl programowania

72 3. ZBĘDNE DZIEDZICZENIE

LISTING 3.5.

Stos w postaci szablonu klasy Stack

+ 19:;

! E E& ")1+ *-)*

%)E*E)%*0

! EE"")*1141E230

! EE""-)* 230

! EE"")E*)*277310

! EEE"")*)4*28830

Powyższa deklaracja powoduje utworzenie obiektu o nawie F+ będącego stosem doprzechowywania maksymalnie 10 elementów typu , natomiast:

F+):4*

Page 23: C++. Styl programowania

PODSUMOWANIE 73

spowoduje utworzenie obiektu o nazwie F+, czyli stosu do przechowywania maksymalnie20 wartości typu . Typ argumentu metody odkładającej na stos )* oraz typ wartości zwra-canej metody zdejmującej ze stosu )* są także określone za pomocą nazwy typu E.

Podstawowym i zasadniczym motywem, który spowodował dodanie szablonów do C++ byłazapewniana przez szablony obsługa ogólnych klas — kolekcji. W ten sposób można utworzyć nietylko stos liczb całkowitych i znaków, lecz równie dobrze można zbudować stosy złożone z liczbzmiennoprzecinkowych, ze wskaźników do znaków, itd.

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 oraz . Oryginalny konstruktor z klasy mógłbyobierać drugi argument, który mógłby określać łańcuch znaków przeznaczony do umieszczenia() na stosie. Jednakże w klasie nie ma żadnego konstruktora, który mógłby pobieraćtaki (analogiczny) argument. Jeśli zastosujemy szablon do opisu obu tych klas jednocześnie, niema żadnego sposobu, by wyrazić takie zróżnicowanie.

PodsumowaniePierwszą i najważniejszą zmianą, której dokonaliśmy w pierwszym programie było zmodyfikowa-nie abstrakcyjnej konstrukcji zdefiniowanej poprzez klasę . Oryginalny interfejs był zdefi-niowany w kategoriach wskaźników do elementów tablicy użytkownika. Abstrakcyjna konstrukcjastosu 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, nazwanaB, 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 abstrakcjaokazał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 danych.

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 B okazał się dlaklas oraz rozwiązaniem prostszym, a umożliwiającym im wykorzystaniefunkcjonalności klasy B.

BibliografiaModel „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).

Page 24: C++. Styl programowania

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.

Ćwiczenie3.1. Klasa B może być albo prywatną klasą bazową, albo posłużyć do utworzenia

prywatnego obiektu składowego wewnątrz klas oraz . 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.