Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00085 007771 10476509 na godz. na dobę w sumie
C++. Styl i technika zaawansowanego programowania - książka
C++. Styl i technika zaawansowanego programowania - książka
Autor: Liczba stron: 480
Wydawca: Helion Język publikacji: polski
ISBN: 83-7361-322-6 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c++ - programowanie
Porównaj ceny (książka, ebook, audiobook).

Zakładając znajomość podstaw języka C++ książka ta umożliwia programistom rozwinięcie zaawansowanych umiejętności programowania poprzez stosowanie styli i idiomów języka C++. Struktura książki zorganizowana jest wokół abstrakcji wspieranych przez język C++: abstrakcyjnych typów danych, kombinacji typów w strukturach dziedziczenia, programowania obiektowego i dziedziczenia wielokrotnego. W książce przedstawione zostają także te idiomy, które nie znajdują bezpośredniego wsparcia w języku C++, takie jak wirtualne konstruktory, obiekty prototypów i zaawansowane techniki odzyskiwania nieużytków.

Książka:

Książka ta jest ważnym podręcznikiem dla każdego programisty aplikacji lub programisty systemowego posługującego się językiem C++.

O autorze:
James Coplien pracuje w departamencie związanym z badaniami nad produkcją oprogramowania w firmie AT&T Bell Laboratories. Jest także konsultantem przy dużych projektach wykorzystujących technologie obiektowe. [więcej...\

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 i technika zaawansowanego programowania Autor: James O. Coplien T³umaczenie: Jaromir Senczyk ISBN: 83-7361-322-6 Tytu³ orygina³u: Advanced C++ Programming Styles and Idioms Format: B5, stron: 480 Zak³adaj¹c znajomoġæ podstaw jêzyka C++ ksi¹¿ka ta umo¿liwia programistom rozwiniêcie zaawansowanych umiejêtnoġci programowania poprzez stosowanie styli i idiomów jêzyka C++. Struktura ksi¹¿ki zorganizowana jest wokó³ abstrakcji wspieranych przez jêzyk C++: abstrakcyjnych typów danych, kombinacji typów w strukturach dziedziczenia, programowania obiektowego i dziedziczenia wielokrotnego. W ksi¹¿ce przedstawione zostaj¹ tak¿e te idiomy, które nie znajduj¹ bezpoġredniego wsparcia w jêzyku C++, takie jak wirtualne konstruktory, obiekty prototypów i zaawansowane techniki odzyskiwania nieu¿ytków. Ksi¹¿ka: • Przedstawia zalety i potencjalne pu³apki zaawansowanych technik programowania w jêzyku C++. • Sposoby efektywnego ³¹czenia abstrakcji jêzyka C++ ilustruje szeregiem krótkich, ale stanowi¹cych wystarczaj¹cy instrukta¿ przyk³adów. • Dostarcza wielu praktycznych zasad wykorzystania jêzyka C++ do implementacji rezultatów projektowania obiektowego. • Omawia wszystkie w³aġciwoġci edycji 3.0 jêzyka C++, w tym zastosowanie szablonów w celu wielokrotnego wykorzystania kodu. • Przedstawia istotne aspekty rozwoju z³o¿onych systemów, w tym projektowanie bibliotek, obs³ugê wyj¹tków i przetwarzanie rozproszone. Ksi¹¿ka ta jest wa¿nym podrêcznikiem dla ka¿dego programisty aplikacji lub programisty systemowego pos³uguj¹cego siê jêzykiem C++. Spis treści Przedmowa......................................................................................... 9 Rozdział 1. Wprowadzenie .................................................................................. 15 1.1. Ewolucja języka C++ ...................................................j..............................................15 1.2. Idiomy jako sposób na złożoność problemów ...................................................j........16 1.3. Obiekty lat 90-tych...................................................j..................................................18 1.4. Projektowanie i język programowania...................................................j....................19 Bibliografia..........................................j...................................................j...........................20 Rozdział 2. Abstrakcyjne typy danych ................................................................. 21 2.1. Klasy.........................................j...................................................j...............................22 2.2. Inwersja obiektowa ...................................................j.................................................25 2.3. Konstruktory i destruktory ...................................................j......................................28 2.4. Funkcje rozwijane w miejscu wywołania ...................................................j...............32 2.5. Inicjacja statycznych danych składowych...................................................j...............34 2.6. Statyczne funkcje składowe ...................................................j....................................35 2.7. Zakresy i słowo kluczowe const...................................................j..............................36 2.8. Porządek inicjacji obiektów globalnych, stałych i składowych statycznych .............38 2.9. Słowo const i funkcje składowe ...................................................j..............................39 2.10. Wskaźniki funkcji składowych ...................................................j.............................41 2.11. Konwencje programowania...................................................j...................................45 Ćwiczenia ...................................................j...................................................j....................46 Bibliografia..........................................j...................................................j...........................47 Rozdział 3. Konkretne typy danych ..................................................................... 49 3.1. Ortodoksyjna postać kanoniczna klasy ...................................................j...................50 3.2. Zakresy i kontrola dostępu ...................................................j......................................56 3.3. Przeciążanie — zmiana semantyki funkcji i operatorów ...........................................59 3.4. Konwersja typu ...................................................j...................................................j....64 3.5. Zliczanie referencji i zmienne wykorzystujące „magiczną” pamięć .........................67 3.6. Operatory new i delete ...................................................j............................................80 3.7. Separacja tworzenia instancji i jej inicjacji ...................................................j.............85 Ćwiczenia ...................................................j...................................................j....................88 Bibliografia..........................................j...................................................j...........................90 Rozdział 4. Dziedziczenie .................................................................................... 91 4.1. Dziedziczenie pojedyncze ...................................................j.......................................93 4.2. Zakresy deklaracji i kontrola dostępu ...................................................j.....................99 4.3. Konstruktory i destruktory ...................................................j....................................109 4.4. Konwersje wskaźników klas ...................................................j.................................112 6 C++. Styl i technika zaawansowanego programowania 4.5. Selektory typu ...................................................j...................................................j....114 Ćwiczenia ...................................................j...................................................j..................116 Bibliografia..........................................j...................................................j.........................118 Rozdział 5. Programowanie obiektowe................................................................ 119 5.1. Funkcje wirtualne...................................................j..................................................121 5.2. Interakcje destruktorów i destruktory wirtualne ...................................................j...128 5.3. Funkcje wirtualne i zakresy...................................................j...................................129 5.4. Funkcje czysto wirtualne i abstrakcyjne klasy bazowe............................................131 5.5. Klasa kopertowa i klasa listu...................................................j.................................133 5.6. Funktory — funkcje jako obiekty ...................................................j.........................161 5.7. Dziedziczenie wielokrotne ...................................................j....................................172 5.8. Kanoniczna postać dziedziczenia...................................................j..........................182 Ćwiczenia ...................................................j...................................................j..................186 Przykład iteratora kolejki ...................................................j.............................................187 Przykład klas prostej aplikacji bankowej...................................................j.......................188 Bibliografia..........................................j...................................................j.........................190 Rozdział 6. Projektowanie obiektowe ................................................................ 191 6.1. Typy i klasy...................................................j...................................................j........192 6.2. Czynności projektowania obiektowego ...................................................j................196 6.3. Analiza obiektowa i analiza dziedziny...................................................j..................199 6.4. Związki obiektów i klas ...................................................j........................................202 6.5. Podtypy, dziedziczenie i przekazywanie...................................................j...............210 6.6. Praktyczne zasady tworzenia podtypów, stosowania dziedzjiczenia i niezależności klas ...................................................j...................................................j.229 Ćwiczenia ...................................................j...................................................j..................231 Bibliografia..........................................j...................................................j.........................232 Rozdział 7. Ponowne użycie i obiekty ................................................................ 233 7.1. Gdy analogie przestają działać...................................................j..............................235 7.2. Projektowanie z myślą o ponownym użyciu...................................................j.........237 7.3. Cztery mechanizmy ponownego użycia kodu...................................................j.......239 7.4. Typy parametryczne czyli szablony...................................................j......................241 7.5. Ponowne użycie i dziedziczenie prywatne...................................................j............249 7.6. Ponowne użycie pamięci...................................................j.......................................252 7.7. Ponowne użycie interfejsu — warianty ...................................................j................253 7.8. Ponowne użycie, dziedziczenie i przekazywanie...................................................j..255 7.9. Ponowne użycie kodu źródłowego...................................................j........................256 7.10. Ogólne uwagi na temat ponownego użycia...................................................j.........259 Ćwiczenia ...................................................j...................................................j..................260 Bibliografia..........................................j...................................................j.........................261 Rozdział 8. Programowanie za pomocą przykładów ........................................... 263 8.1. Przykład — przykłady pracowników...................................................j....................266 8.2. Konstruktory ogólne — idiom zespołu przykładów ................................................271 8.3. Autonomiczne konstruktory ogólne ...................................................j......................273 8.4. Abstrakcyjne przykłady bazowe ...................................................j...........................275 8.5. Ku idiomowi szkieletu przykładu ...................................................j.........................278 8.6. Uwagi na temat notacji...................................................j..........................................280 8.7. Przykłady i administracja kodem programu...................................................j..........282 Ćwiczenia ...................................................j...................................................j..................283 Prosty parser wykorzystujący przykłady...................................................j......................284 Przykład wykorzystujący szczeliny ...................................................j.............................286 Bibliografia..........................................j...................................................j.........................288 Spis treści 7 Rozdział 9. Emulacja języków symbolicznych w C++ ......................................... 289 9.1. Przyrostowy rozwój programów w języku C++ ...................................................j...291 9.2. Symboliczna postać kanoniczna...................................................j............................293 9.3. Przykład — ogólna klasa kolekcji...................................................j.........................304 9.4. Kod i idiomy obsługujące mechanizm ładowania przyrostowego...........................308 9.5. Odzyskiwanie nieużytków ...................................................j....................................318 9.6. Hermetyzacja typów podstawowych...................................................j.....................327 9.7. Wielometody i idiom symboliczny ...................................................j.......................328 Ćwiczenia ...................................................j...................................................j..................332 Bibliografia..........................................j...................................................j.........................333 Rozdział 10. Dynamiczne dziedziczenie wielokrotne ............................................ 335 10.1. Przykład — system okienkowy...................................................j...........................336 10.2. Ograniczenia...................................................j...................................................j.....339 Rozdział 11. Zagadnienia systemowe.................................................................. 341 11.1. Statyczne projektowanie systemów ...................................................j....................342 11.2. Dynamiczne projektowanie systemów...................................................j................350 Bibliografia..........................................j...................................................j.........................365 Dodatek A Język C w środowisku języka C++ .................................................. 367 A.1. Wywołania funkcji ...................................................j...............................................367 A.2. Parametry funkcji ...................................................j.................................................368 A.3. Prototypy funkcji...................................................j..................................................369 A.4. Przekazywanie parametrów przez referencję ...................................................j.......370 A.5. Zmienna liczba parametrów ...................................................j.................................371 A.6. Wskaźniki funkcji...................................................j.................................................373 A.7. Słowo kluczowe const jako modyfikator typu ...................................................j.....375 A.8. Interfejs z programami w języku C ...................................................j......................377 Ćwiczenia ...................................................j...................................................j..................389 Bibliografia..........................................j...................................................j.........................390 Dodatek B Reprezentacja figur geometrycznych w języku C++ ......................... 391 Dodatek C Referencje jako wartości zwracane przez operatory......................... 403 Dodatek D Kopiowanie „bit po bicie”................................................................ 407 D.1. Dlaczego kopiowanie składowych nie rozwiązuje problemu?................................408 Dodatek E Figury geometryczne i idiom symboliczny........................................ 409 Dodatek F Programowanie strukturalne w języku C++ ..................................... 447 F.1. Programowanie strukturalne — wprowadzenie...................................................j....447 F.2. Elementy programowania strukturalnego w języku C++ ........................................448 F.3. Alternatywa dla bloków z głęboko zagnieżdżonymi zakresami..............................451 F.4. Rozważania na temat implementacji...................................................j.....................455 Ćwiczenia ...................................................j...................................................j..................456 Gra wykorzystująca idiom strukturalny ...................................................j.......................457 Bibliografia..........................................j...................................................j.........................460 Spis rysunków ................................................................................ 461 Spis listingów ................................................................................. 463 Skorowidz....................................................................................... 467 Rozdział 9. Emulacja języków symbolicznych w C++ Język C++ dysponuje mechanizmami umożliwiającymi definiowanie abstrakcyjnych typów danych i używanie ich w programowaniu obiektowym. Jednak elastyczność języków obiektowych wysokiego poziomu takich jak Smalltalk czy CLOS jest trudna w języku C++ tak blisko związanym z językiem C. Zarówno w języku C, jak i C++ nazwa zmiennej związana jest z adresem opisywanego przez nią obiektu i tym samym nie jest jedynie jego etykietą, która może zostać „odklejona” z jednego obiektu i przy- porządkowana innemu. Silne powiązanie zmiennej z obiektem pozwala kompilatorowi zapewnić, że dana zmienna zawsze będzie używana z obiektem określonego typu. Właściwość ta pozwala generować bardziej efektywny kod i zapobiegać użyciu obiektów tam, gdzie nie były one spodziewane. Efektywność i zgodność typów osiąga się jednak kosztem elastyczności działania programu — na przykład zmienna zadeklarowana jako typu HNQCV nie może zostać użyta podczas działania programu dla obiektu typu QORNGZ, chociaż oba typy są ze sobą zgodne pod względem zsachowania. Smalltalk i większość języków obiektowych bazujących na języku Lisp dysponuje dwiema właściwościami wykorzystującymi luźne powiązanie zmiennych i obiektów, które nie są bezpośrednio dostępne w języku C++, ale mogą zostać wyrażone za pomocą odpowiednich idiomów i styli programowania. Pierwszą z tych właściwości jest auto- matyczne zarządzanie pamięcią (zliczanie referencji lub zbieranie nieużytków). W języ- kach symbolicznych, gdzie zmienne są jedynie etykietami obiektów, czas istnienia obiektów jest niezależny od opisujących je zmiennych. Środowiska programowania w językach symbolicznych używają specjalnych technik pozwalających odzyskać pamięć zajmowaną przez obiekty, do których nie istnieją już żadne odwołania w pro- gramie. W języku C++ możemy symulować takie rozwiązanie, adresując obiekty za pomocą wskaźników. Utworzony w ten sposób dodatkowy poziom dostępu do obiektów może zostać wykorzystany w celu automatyzacji zarządzania pamięcią, co zostało szczegółowo omówione w podrozdziałach 3.5 i 3.6. 290 C++. Styl i technika zaawansowanego programowania Drugą istotną właściwością języków obiektowych wysokiego poziomu jest wysoki stopień polimorfizmu. Idiomy umożliwiające podobnie zaawansowany polimorfizm zostały omówione szczegółowo w podrozdziale 5.5. Zaawansowany polimorfizm umożliwia tworzenie bardziej elastycznych architektur systemów. Obiekty stają się słabiej powiązane i dlatego łatwiej jest nimi zarządszać. Automatyczne zarządzanie pamięcią i zaawansowany polimorfizm stanowią o sile języków programowania opartych o Lisp, języka Smalltalk i innych języków progra- mowania obiektowego o wywodzących się z tradycji programowania symbolicznego. Podobną elastyczność możemy także uzyskać w programach tworzonych w języku C++ w stopniu, na jaki pozwala nam emulacja wymienionych właściwości za pomocą odpowiednich idiomów. Elastyczność taka ma jednak swoja cenę — zawsze odbywa się kosztem szybkości działania programu i większego zapotrzebowania na pamięć. Również wykrywanie błędów wykonywane dotychczas przez system typów podczas kompilacji programu zostaje odroczone do momentu wykonania idiomów i w związku z tym zależy od spójności kodu kontrolującego zgodność typów w programie użyt- kownika, a nie od kompilatora. Doświadczenie projektanta pozwala osiągnąć w tej mierze wymagany kompromis poprzez wybór idiomów odpowiednich do potrzeb konkretnej aplikacji. W bieżącym rozdziale omówione zostaną trzy rodzaje idiomów. Pierwszy z nich stanowi kontynuację koncepcji przedstawionych w poprzednich rozdziałach, a wspierających przyrostowy rozwój programu poprzez redukcję wpływu zmian. Przedstawiona zostanie postać kanoniczna tego idiomu, która stanowić będzie podstawę dla pozostałych dwóch rodzajów. Drugi z nich umożliwi przyrostową aktualizację programu za pomocą pro- stego środowiska czasu wykonania. Natomiast trzeci wykorzystywać będzie techniki automatyzacji zwalniania nieużywanych obiektów i odzyskiwania ich zasobów. Każdy z tych idiomów może być stosowany niezależnie bądź w połączeniu z pozostałymi idiomami. Implementacja drugiego z wymienionych idiomów na dowolnej platformie wymaga sporo wiedzy i wysiłku, ponieważ zależy od szczegółów implementacji języka C++ związanych ze sposobem reprezentacji klas. Implementacje przedstawione w tym roz- dziale oparte są na objaśnieniach do standardu ANSI języka C++ [1]. Zostały one uru- chomione w środowisku AT T USL C++ Compilation System Release 3 i powinny być przenośne do wielu środowisk przy wykorzystaniu kompilatorów innych producentów. Należy zaznaczyć, że technik prezentowanych w niniejszym rozdziale nie należy traktować jako substytutów rozwiązań oferowanych w językach Smalltalk lub CLOS. Języki symboliczne oprócz elastyczności umożliwiającej przyrostowy rozwój pro- gramów posiadają również rozbudowane środowiska programowania wyposażone we własne, zaawansowane narzędzia obsługujące przyrostowy rozwój oprogramowania. Przedstawione tutaj rozwiązania pozwalają tylko w pewnym stopniu zbliżyć język C++ do tych możliwości, ale za cenę dodatkowej dyscypliny w kodowaniu i kosztem słabszej efektywności tworzonych programów. Zadaniem tego rozdziału jest wprowadzenie koncepcji wspierających możliwości przyrostowego rozwoju programów w języku C++ oraz umożliwiających elastyczną aktualizację aplikacji pracujących w trybie ciągłym. Przedstawione rozwiązania mogą również posłużyć jako model kodu generowanego Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 291 automatycznie przez narzędzia współpracujące z generatorem aplikacji lub kompilator języka wysokiego poziomu służącego do tworzenia elastycznych i interaktywnych aplikacji. 9.1. Przyrostowy rozwój programów w języku C++ Zadaniem przyrostowego rozwoju programów jest szybkie wprowadzanie zmian tak, by ciągłość procesu rozwoju programów nie była zakłócana procesem testowania nowych wersji. Szybkie iteracje wersji programu stanowią ważną technikę udoskonalania pro- gramu i kontrolę jego zachowań w świetle nowych wymagań. Koszt przyrostowej zmiany musi być przy tym niski, aby iteracje takie były efekstywne. Przyrostowość i projektowanie obiektowe Idea przyrostowego rozwoju programów doskonale współgra z projektowaniem obiek- towym. Hermetyzacja szczegółów implementacji wewnątrz klas sprawia, że stają się one naturalnymi jednostkami iteracji. Istnienie wspólnego protokołu umożliwiającego posługiwanie się wszystkimi klasami danej hierarchii dziedziczenia umożliwia łatwe dodawanie nowych klas. Choć wszystko to sprzyja przyrostowemu tworzeniu pro- gramów w języku C++, to jednak same iteracje ze względu na konieczność ponownej kompilacji kodu mogą okazać się zdecydowanie wolniejsze niż językach Smalltalk czy CLOS. Obecnie coraz częściej powstają zaawansowane środowiska programowania w języku C++, które, zrywając z tradycyjną technologią tworzenia oprogramowania, umożliwiają przyrostowy rozwój programów. Jednak technologia taka nadal nie jest jeszcze dostępna dla wielu platform. Na przykład elastyczność rozwoju lub aktualizacji pożądana jest najczęściej w systemach wbudowanych w pewne urządzenia pracujące poza kontekstem zawansowanych systemów operacyjnych i narzędzi programistycznych. Chociaż więc przedstawione w tym rozdziale rozwiązania w zakresie przyrostowego rozwoju programów nie będą posiadać takich możliwości jak oferowane przez zaawan- sowane środowiska programowania przyrostowego w języku C++, to jednak umożliwiać będą przyrostowy rozwój dla szerszego spektrum platsform i systemów. Redukcja kosztów kompilacji Pierwszy krok na drodze do programowania przyrostowego w języku C++ musi polegać na redukcji kosztów wynikających z ponownej kompilacji kodu. Najbardziej efektyw- nym sposobem realizacji tego zadania będzie ograniczenie samej potrzeby ponownej kompilacji. Pomiędzy zmienną, jej typem i sposobem reprezentacji zachodzi w języku C++ silny związek ustalany w momencie kompilacji. Jeśli na skutek ewolucji programu zmieni się na przykład typ zmiennej, to kod posługujący się taką zmienną musi zostać ponownie skompilowany. Zmiana reprezentacji zmiennej na skutek zmiany jej typu może również spowodować przesunięcie adresów innych zmiennych i tym samym wymusić 292 C++. Styl i technika zaawansowanego programowania ponowną kompilację jeszcze innych fragmentów kodu, które posługują się tymi zmien- nymi. Na przykład jakakolwiek zmiana interfejsu klasy wymusza zawsze ponowną kom- pilację każdego kodu, który korzysta z jakiegokolwiek elementu tego interfejsu. Większość rozwiązań służących ograniczaniu ponownej kompilacji kodu polega na tworzeniu pośredniej warstwy dostępu do symboli. Przykładami takich rozwiązań mogą być, na przykład, idiom koperty i listu (podrozdział 5.5) i jego pochodne, takie jak idiom przykładu omówiony w rozdziale 8. Idiomy przedstawione w bieżącym rozdziale bazują w znacznej mierze właśnie na idiomie przykładu. Zapewnienie odpowiedniej elastyczności w obliczu zmian i przy minimalnym poziomie kompilacji odbywa się za cenę mniejszej efektywności działania programu wynikającej z zastosowania dodatkowych poziomów dostępu do jego symboli. Zgodnie z duchem języków symbolicznych rozwiązania takie oferują również słabszą kontrolę zgodności typów w stosunku do tradycyjnego programowania obiektowego w języku C++. Osią- gnięcie odpowiedniego kompromisu możliwe jest przez wybór właściwych idiomów dla konkretnej aplikacji. Redukcja kosztów konsolidacji i ładowania Drugi krok na drodze ku przyrostowemu rozwojowi programów w języku C++ polega na redukcji czasu związanego z konsolidacją i ładowaniem kodu. Konsolidacją nazy- wamy etap tworzenia wykonywalnego pliku programu z relokowalnych plików wyni- kowych powstałych podczas kompilacji, natomiast ładowanie polega na umieszczeniu wykonywalnego kodu programu w pamięci w celu jego wykonania. W niektórych systemach etapy te traktuje się łącznie, a większość systemów wykonuje podczas nich wiązanie symboli z adresami. Efektywność konsolidacji i ładowania jest szczególnie istotna w przypadku tworzenia złożonych systemów, dla którym również techniki obiektowe mają najwięcej do zaofe- rowania. Przyrostowa konsolidacja i ładowanie kodu nie jest silną stroną większości tradycyjnych systemów mikroprocesorowych, jednak wiele nowych wersji systemu UNIX oraz innych systemów umożliwia już przyrostową konsolidację programów. W rezultacie konsolidacji przyrostowej powstają zwykle mniejsze moduły wynikowe, a przede wszystkim umożliwia ona szybsze zmiany niżs pełna konsolidacja. Nawet wtedy, gdy konsolidacja jest wystarczająco szybka, wąskim gardłem może okazać się proces ładowania kodu. Jeśli inicjacja systemu trwa długo, to nawet przyrostowo konsolidowane zmiany wymagają sporo czasu dla każdej iteracji. Natomiast w przy- padku, gdy kod może być ładowany przyrostowo do zainicjowanego i działającego programu, to wprowadzanie zmian odbywa się zdecydowsanie szybciej. Szybkie iteracje Szybkie iteracje stanowią najefektywniejsze rozwiązanie na etapie poszukiwania docelowej architektury rozwiązania. Powstające w ten sposób tymczasowe prototypy służą głownie kontroli właściwego rozumienia aplikacji przez projektanta. Tworzenie Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 293 takich prototypów, przy założeniu pewnych ograniczeń związanych ze stabilnością powstającej struktury rozwiązania, może być samo w sobie osobną dziedziną w pro- cesie rozwoju oprogramowania. Jeśli szybkie iteracje kodu są właściwie zarządzane, to mogą stanowić efektywną technikę rozwoju systemu. Natomiast jeśli dotyczą one za każdym razem zasadniczych interfejsów tworzonego systemu, to będą jedynie zwiększać entropię i systematycznie niszczyć struktusrę systemu. 9.2. Symboliczna postać kanoniczna Idiom symboliczny jest alternatywą ortodoksyjnej postaci kanonicznej zaprezentowanej w podrozdziale 3.1 (strona 50). Emulacja paradygmatu symbolicznego w języku C++ nie jest „ortodoksyjna”, ale wymaga pewnych konwencji. Posługując się tą alternatywną postacią kanoniczną, możemy w języku C++ modelować wiele właściwości charakte- rystycznych dla symbolicznych języków programowania. Jednak forma ta traci nieco na zwartości wyrazu charakterystycznej dla ortodokssyjnej postaci kanonicznej. Kiedy używać tego idiomu? Idiom ten stosujemy, gdy pożądana jest elastyczność oraz przyrostowość charakterystyczna dla języków programowania symbolicznego. Idiom ten może być również używany jako szkielet interfejsu pomiędzy środowiskiem programowania w języku C++ a środowiskami programo- wania w językach symbolicznych. Idiomy i style przedstawione w tym rozdziale mogą zostać wykorzystane do budowy własnego środowiska tworzenia prototypów w języku C++, jeśli pod- czas tworzenia aplikacji będziemy stosować się do psewnych konwencji. Idiom ten znajduje również zastosowanie w przypadku systemów pracy ciągłej, umożliwiając ich aktualizację bez zatrzymy- wania oraz ewolucję w dłuższym horyzoncie czasowym. Omawiana tu postać kanoniczna bazuje na koncepcjach w zakresie zarządzania pamię- cią i polimorfizmu przedstawionych w poprzednich rozdziałach i uzupełnia je tak, by mogły obsługiwać przyrostowość. Do głównych aspektów postsaci kanonicznej należą:  Automatyczne zarządzanie pamięcią wykorzystujące klsasy listu ze zliczaniem referencji (podrozdział 3.5).  Likwidacja dostępu do obiektów za pomocą wskaźników sprzy jednoczesnym udostępnieniu zachowania obiektów charakterystycznesgo dla wskaźników (podrozdział 3.5).  Wykorzystanie funkcji wirtualnych dla uzyskania elasstyczności w zakresie ładowania i wykonania kodu.  Wykorzystanie idiomu przykładu (rozdział 8.). Symboliczną postać kanoniczną tworzy niewielka kolekcja klas bazowych, które wykorzystywane są do tworzenia klasy kopertowej i klasy listu dla danej aplikacji. Deklaracje klas bazowych umieszczone zostaną w globalnym pliku nagłówkowym k.h przedstawionym na listingu 9.1. Plik ten zawiera deklarację dwóch klas — 6QR, która jest klasą bazową dla klas kopertowych, oraz 6JKPI, która służy jako klasa bazowa klas listu. 294 C++. Styl i technika zaawansowanego programowania Listing 9.1. Plik nagłówkowy k.h plik nagłówkowy k.h ENCUU6QR] RWDNKE Obiekty tej klasy nie posiadają danych oprócz __vptr dostarczanej przez kompilator. Ponieważ wszystkie inne klasy powstają jako pochodne klasy Top, to dla większości implementacji pole __vptr będzie pierwszym elementem każdego obiektu. Niektóre implementacje mogą wymagać innego mechanizmóu dostępu do __vptr. Dla idiomu symbolicznego od właściwóości tej zależy jedynie aspekt dynamicznego ładowania. XKTVWCN`6QR ] RWUV[ _ XKTVWCN6QR V[RG ]TGVWTPVJKU_ operator delete jest dostępny publicznie ze względu na konieczność usuwania aktualizowanych obiektów UVCVKEXQKFQRGTCVQTFGNGVG XQKF R ] QRGTCVQTFGNGVG R  _ RTQVGEVGF 6QR ] RWUV[ _ UVCVKEXQKF QRGTCVQTPGY UKGAVN ] TGVWTPQRGTCVQTPGY N  _ _ V[RGFGHWPUKIPGFNQPI4 (A6;2  ENCUU6JKPIRWDNKE6QR] Wszystkie składowe dziedziczone po klasie Thing Definiuje postać kanoniczną klas listu RWDNKE 6JKPI TGH QWPV8CN  WRFCVG QWPV8CN  ]_ XKTVWCN4 (A6;2 FGTGH ]zmniejsza licznik referencji TGVWTPŌTGH QWPV8CN _ XKTVWCN4 (A6;2 TGH ]zwiększa licznik referencji TGVWTP TGH QWPV8CN _ XKTVWCN6JKPI EWVQXGT 6JKPI funkcja aktualizacji klasy XKTVWCN`6JKPI ] RWUV[ _destruktor RTKXCVG 4 (A6;2 TGH QWPV8CNWRFCVG QWPV8CN _ Klasa 6JKPI sama także jest klasą pochodną klasy 6QR, co pozwala zapewnić rozwiązaniu jednolitość i przypomina rozwiązanie stosowane w wielu językach symbolicznych polegające na istnieniu wspólnej klasy bazowej, od której wywodzą się wszystkie inne klasy. Zadanie klas 6QR i 6JKPI przypomina pod tym względem rolę klas 1DLGEV, NCUU i $GJCXKQT w języku Smalltalk, chociaż dokładne odwzorowanie pomiędzy tymi klasami nie jest ani oczywiste, ani przydatne. Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 295 Klasy te zapewniają elastyczność, wspierają zarządzanie pamięcią, aktualizację w trakcie wykonania oraz luźny model typów charakterystyczny dla języków symbolicznych. Każda z klas zostanie omówiona szczegółowo w następnsych dwóch podrozdziałach. Klasa Top Klasa 6QR znajduje się na szczycie hierarchii klas systemu. Wszystkie klasy systemu są wobec tego jej pochodnymi. Klasa 6QR nie posiada własnych, jawnych danych. Większość kompilatorów języka C++ umieszcza w jej obiektach jedynie niejawną składową wykorzystywaną do rozpoznania typu obiektu przez mechanizm funkcji wirtualnych. Składowa ta nosi nazwę XRVT i jest wskaźnikiem elementu tablicy funkcji wirtualnych noszącej nazwę XVDN. Klasa Top posiada wirtualną metodę składową, aby wymusić obecność takiego wskaźnika. Różne implementacje języka C++ mogą różnie implementować mechanizm funkcji wirtualnych, ale rozwiązania te różnią się co najwyżej w szczegółach. Rozważmy przypadek następujących trzech klas [1]: ENCUU#] RWDNKE KPVC XKTVWCNXQKFH KPV  XKTVWCNXQKFI KPV  XKTVWCNXQKFJ KPV  _ ENCUU$RWDNKE#] RWDNKE KPVD XQKFI KPV  _ ENCUU RWDNKE$] RWDNKE KPVE XQKFJ KPV  _ W oparciu o powyższe deklaracje możemy spodziewać się, że reprezentacja obiektu klasy C w pamięci będzie wyglądać w następujący spossób: Jeśli klasa znajdująca się na szczycie hierarchii nie posiada własnych danych, to wskaź- nik XRVT łatwo jest odnaleźć, ponieważ znajduje się na początku reprezentacji obiektu. Dysponując wskaźnikiem takiego obiektu, dowolna funkcja może uzyskać wartość wskaźnika XRVT i użyć ją do przeglądania zawartości tablicy XVDN dla klasy obiektu. Możliwość ta jest kluczowa z punktu widzenia zastępowanie funkcji podczas działania programu. 296 C++. Styl i technika zaawansowanego programowania Klasa 6QR posiada również domyślny (bez parametrów) konstruktor zadeklarowany w sekcji o dostępie protected, co zapobiega bezpośredniemu tworzeniu instancji tej klasy. Posiada również wirtualny destruktor, którego ciało nie zawiera żadnych instrukcji. Destruktor został zadeklarowany jako wirtualny, aby zapewnić wywoływanie destrukto- rów odpowiednich klas podczas wykonania programu. Deklaracja operatora PGY również została umieszczona w sekcji RTQVGEVGF, aby zapewnić, że obiekty klas kopertowych nie będą tworzone na stercie. Ograniczenie deklaracji obiektów klas kopertowych wyłącznie do obiektów lokalnych, globalnych lub składo- wych innych obiektów pozwala kompilatorowi całkowicie zautomatyzować ich usu- wanie. Jeśli instancje klas kopertowych powinny być również tworzone na stercie, to zawsze istnieje możliwość przesłonięcia tej deklaracji w klasach pochodnych. Dyna- miczny przydział i zwalnianie pamięci klas należących do hierarchii klasy listu odbywa się za pomocą operatorów klasy listu. Funkcja składowa V[RG jest przesłaniana przez klasy pochodne tak, by zwracała wskaź- nik odpowiedniego przykładu. Jest on wykorzystywany w celu aktualizacji klasy w czasie działania programu, co zostanie omówione w dalszej cszęści tego rozdziału. Działanie klasy 6QR jest w znacznym stopniu zależne od implementacji kompilatora. Większość kompilatorów języka C++ bazuje na formacie obiektów opisanym powyżej, ale w ogólnym przypadku przeniesienie implementacji tej klasy do dowolnego śro- dowiska może wymagać dodatkowych wysiłków. Klasa Thing Klasa 6JKPI spełnia rolę klasy bazowej dla wszystkich klas listu. Ponieważ klasy listu zawierają zasadniczą część inteligencji danej aplikacji, to większość semantyki zwią- zanej z dynamiką obiektów znajduje się w publicznym interfejsie klasy 6JKPI. W idiomie symbolicznym nawet pewna funkcjonalność związana z zarządzaniem pamięcią — która zwykle umieszczana bywa w klasie kopertowej — implementowana jest w klasach pochodnych klasy 6JKPI. Funkcje FGTGH i TGH operują na prywatnym liczniku referencji TGH QWPV8CN. Zadekla- rowane są jako funkcje wirtualne, aby klasy pochodne mogły je przesłonić. Jednak typowa aplikacja idiomu symbolicznego z reguły nie musi ich deklarować jako funkcji wirtualnych, a nawet może zadeklarować je jako funkcje rozwijane w miejscu wywoła- nia. Funkcje te istnieją bowiem głównie dla wygody programisty. Prywatna składowa WRFCVG QWPV8CN wykorzystywana jest podczas ładowania przyrostowego, a sposób jej użycia zostanie opisany w dalszej części rozdziału. Funkcja EWVQXGT wykorzystywana jest do przekształcenia istniejącego obiektu danej klasy w obiekt reprezentujący inną wersję tej samej klasy. Umożliwia to konwersję danych istniejącego obiektu do nowego formatu, gdy do systemu zostaje wprowadzona nowa wersja klasy. Funkcja ta jest zwykle przesłaniana w klasach pochodnych, jeśli jest rzeczywiście używana. Jej domyślna semantyka polega jedynie na zwróceniu wskaźnika oryginalnego obiektu. Jej zastosowanie zostanie omówione w dalszej części rozdziału. Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 297 Wirtualny destruktor został zadeklarowany jedynie w celu zapewnienia, że dla obiektów klas pochodnych klasy 6JKPI wykonywany będzie odpowiedni kod na skutek wywo- łania operatora FGNGVG. Symboliczna postać kanoniczna klas aplikacji Dysponując szkieletem złożonym z klas zadeklarowanych w pliku k.h, możemy scharak- teryzować postać kanoniczną klas aplikacji wykorzystywaną przez idiom symboliczny. Będzie ona dotyczyć dwóch podstawowych rodzajów klas — kopert, które zarządzają tworzeniem i przypisywaniem obiektów, oraz listów, które zawierają zasadniczą seman- tykę aplikacji. Klasa kopertowa może być związana z wieloma klasami listu. Załóżmy, że projekt określa typ 0WODGT jako typ bazowy dla typów QWDNG, $KI+PVGIGT i QORNGZ. Tradycyjne rozwiązanie w języku C++ wykorzystujące dziedziczenie i funkcje wirtualne umożli- wia wymienne posługiwanie się obiektami wymienionych klas za pomocą interfejsu klasy 0WODGT. Klasa 0WODGT będzie w nim abstrakcyjną klasą bazową dla pozostałych trzech klas, a instancje klasy 0WODGT nie będą występować. W rozwiązaniu opartym o idiom symboliczny użytkownik zachowuje możliwość wymiennego posługiwania się obiektami wymienionych trzech klas za pomocą interfejsu 0WODGT. Jednak klasa 0WODGT służy równocześnie za kopertę dla obiektów klas listu QWDNG, $KI+PVGIGT i QORNGZ. Klasy te tworzone są jako klasy pochodne ogólnej klasy bazowej 0WOGTKE4GR charak- teryzującej sygnaturę klas pochodnych. Klasa ta stanowi uogólnienie listu dla aplika- cji numerycznej. Klasa kopertowa 0WODGT zawiera wskaźnik typu 0WOGTKE4GR , który dotyczy obiektu listu. Ogólna klasa listu jest z kolei klasą pochodna klasy 6JKPI, a klasa kopertowa (0WODGT) jest pochodną klasy 6QR. Taka struktura tworzy odpowiednie war- stwy pośrednie umożliwiające zaawansowany polimorfizm i aktualizacje podczas wykonania programu. Zwróćmy uwagę, że ponieważ list jest klasą pochodną klasy 6JKPI, a koperta klasą pochodna klasy 6QR, to nie możemy użyć wspólnej klasy bazowej dla listów i kopert, tak jak to bywało w poprzednich przykładach. Symboliczną postać kanoniczną przedstawimy, posługując się ogólnym przykładem, w którym klasa PXGNQRG będzie klasą kopertową, a klasa .GVVGT będzie ogólną klasą bazową listów. Kompozyt złożony z pojedynczego obiektu klasy PXGNQRG i pojedyn- czego obiektu klasy jednej z klas pochodnych klasy .GVVGT stanowić będzie pojedynczą abstrakcję wykorzystywaną jako element programu stossującego idiom symboliczny, Każdy program lub system może posiadać wiele klas kopertowych wykorzystujących postać kanoniczna klasy PXGNQRG i wiele klas listu utworzonych na wzór klasy .GVVGT i posiadających wyspecjalizowaną semantykę. Na przykład klasa 0WODGT może odpo- wiadać klasie PXGNQRG, klasa 0WOGTKE4GR klasie .GVVGT, a klasy QORNGZ i inne będą pochodnymi klasy 0WOGTKE4GR. Ten sam program może również używać klasy 5JCRG stworzonej na wzór klasy PXGNQRG oraz klasy 5JCRG4GR i jej pochodnych tworzących strukturę drzewiastą wzorowaną na hierarchii dziedziczenia klasy .GVVGT. Chociaż klasy 0WODGT i 5JCRG nie mają ze sobą nic wspólnego, to mogą istnieć w jednym programie, a każda z nich używa idiomu symbolicznego we własnysm zakresie. 298 C++. Styl i technika zaawansowanego programowania Klasa PXGNQRG (listing 9.2) stanowi uogólnienie klasy zajmującej się obsługą operacji tworzenia i przypisywania obiektów (czyli w praktyce obsługuje wszelkie operacje kopiowania oraz zajmuje się kwestią przydziału pamięci). Często warto zastosować w jej przypadku ortodoksyjną postać kanoniczną (podrozdział 3.1) i uczynić ją w ten sposób konkretnym typem danych. Koperta przypomina nieco etykietę, która może być stosowana do różnych obiektów. Przypisanie oznacza wtedy powiązanie etykiety z obiektem. Usunięcie ostatniej etykiety obiektu jest równoważne z jego zwróceniem do puli obiektów nieużywanych. Listing 9.2. Klasa Envelope KPENWFGMJz poprzedniego listingu KPENWFGJfunkcje składowe klasy Envelope GZVGTP6JKPI GPXGNQRG NGVVGTwskaźniki przykładów ENCUU PXGNQRGRWDNKE6QR]klasa Top zdefiniowana w k.h RWDNKE .GVVGT QRGTCVQT EQPUV]przekazuje wszystkie operacje TGVWTPTGRobiektowi rep _  PXGNQRG ]TGRNGVVGT OCMG _  PXGNQRG .GVVGT  ` PXGNQRG ] KH TGRTGR FGTGH  FGNGVGTGR _  PXGNQRG PXGNQRGZ ]  TGRZTGR  TGH  _  PXGNQRGQRGTCVQT PXGNQRGZ ] KH TGRZTGR ] KH TGRTGR FGTGH  FGNGVGTGR  TGRZTGR  TGH  _ TGVWTP VJKU _ 6JKPI V[RG ]TGVWTPGPXGNQRG_ RTKXCVG UVCVKEXQKF QRGTCVQTPGY UKGAV ] 5[UA TTQT JGCR PXGNQRG  _ UVCVKEXQKFQRGTCVQTFGNGVG XQKF ]_ .GVVGT TGR _ Klasa kopertowa zachowuje się jak abstrakcja o luźnysm typie, a jej instancje symulują zmienne, które zachowują się jak etykiety, które nie posiadają typu, podobne do sto- sowanych w wielu językach symbolicznych. Na przykład funkcje składowe koperty nie przekazują szczegółowej semantyki obiektu listu, który zawiera koperta. Jednak koperta przyjmuje działanie przechowywanego obiektu klasy listu, posługując się mechanizmem operatora  opisanym w podrozdziale 3.5 w taki sam sposób, jak zmienna języka sym- bolicznego przyjmuje zachowanie obiektu, do którego została „przyklejona”. W jaki sposób interfejs koperty przekazuje jednak wiedzę zawieranego obiektu klasy listu? Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 299 Odpowiedź na to pytanie tkwi w typie zwracanym przez operator  . Typem tym jest .GVVGT . Zwrócony wskaźnik jest jednym z rzeczywiście niewielu wskaźników wystę- pujących w idiomie symbolicznym. Stanowi on jednak tylko przejściową wartość, która nie jest zwykle przechowywana do dalszego użytku. Klasa kopertowa posiada konstruktory, ale zasadnicza inicjacja obiektu wykonywana jest przez funkcje wirtualne OCMG klasy listu. Taki sposób działania jest korzystny z punktu widzenia przyrostowości i zostanie omówiony w dalszej części rozdziału. Konstruktory klasy kopertowej służą jedynie inicjacji oraz konwersji wykonywanej przez kompilator. Dwa z tych konstruktorów są charakterystyczne dla osrtodoksyjnej postaci kanonicznej — konstruktor domyślny (nie posiada parametrów) oraz konstruktor kopiujący. Nie- zbędny jest także konstruktor tworzący nową kopertę dla instancji klas listu. Konstruk- tor ten dokonuje konwersji wyników wewnętrznych obliczeń klas listu na obiekty, które używane są przez klientów kompozytu złożonego z kopserty i listu. Konstruktor kopiujący, operator przypisania i destruktor modyfikują licznik referencji obiektu w sposób opisany w podrozdziale 3.5. Destruktor sprawdza, czy licznik refe- rencji listu jest równy zero. Jeśli tak, to znaczy to, że usuwana jest ostatnia koperta posiadająca referencję tego listu i może być on ususnięty. Ostatnią z funkcji klasy PXGNQRG, ale za to najważniejszą dla jej działania, jest ope- rator  , który automatyzuje przekazywanie wywołań funkcji składowych koperty do obiektu listu. Taki sam efekt uzyskalibyśmy, powielając w interfejsie koperty sygnaturę listu, a każda z funkcji składowych koperty przekazywałaby swoje działanie odpo- wiadającej jej funkcji listu. Jednak rozwiązanie takie wymaga dodatkowego wysiłku związanego z powieleniem funkcji listu w klasie kopsertowej. Klasa .GVVGT (listing 9.3 i 9.4) definiuje interfejs wszystkich klas obsługiwanych za pośrednictwem interfejsu klasy PXGNQRG. Klasa .GVVGT sama jest klasą bazową, zwy- kle abstrakcyjną, dla grupy klas, których obiekty obsługiwane są za pośrednictwem interfejsu klasy PXGNQRG. Jeden obiekt klasy PXGNQRG może być wykorzystywany w cyklu swojego życia jako interfejs dla wielu różnych obiektów listu. Na przykład obiekt klasy 0WODGT może być początkowo interfejsem listu klasy QORNGZ, jednak w następstwie wykonywanych obliczeń lub operacji przypisania obiekt listu może zostać zastąpiony obiektem listu innej klasy. Listing 9.3. Klasa Letter ENCUU.GVVGTRWDNKE6JKPI] RWDNKE  FGMNCTCELGYU[UVMKEJQRGTCVQTÎYFGHKPKQYCP[EJ]RTG  Wľ[VMQYPKMCRQYKPP[PCNGļèUKúYV[OOKGLUEW   GYINúFWPCCUVQUQYCPKGQRGTCVQTC  U[IPCVWTCVGLMNCU[PKGOWUKD[èRQYKGNCPCYMNCUKG PXGNQRG  ,GFPCMPCNGľ[RCOKúVCèCD[UMđCFQYCTGRMNCU[   PXGNQRGD[đCYđCħEKYGIQV[RW  1RGTCVQTRT[RKUCPKCFGHKPKQYCP[LGUVYMNCUKG] PXGNQRG   V[RTGVWTPAV[RGYTCECP[RTGFGMNCTQYCPGVWVCLHWPMELGWľ[VMQYPKMCRQYKPKGP  D[èV[RGORQFUVCYQY[OV[RGO PXGNQRGV[RGO PXGN]QRG  NWDMQPMTGVP[OV[RGOFCP[EJ   300 C++. Styl i technika zaawansowanego programowania XKTVWCNXQKFUGPF 5VTKPIPCOG5VTKPICFFTGUU  XKTVWCNFQWDNGRQUVCIG  XKTVWCNTGVWTPAV[RGHWPMELCAWľ[VMQYPKMC  XKTVWCN PXGNQRGOCMG konstruktor XKTVWCN PXGNQRGOCMG FQWDNG kolejny konstruktor XKTVWCN PXGNQRGOCMG KPVFC[UFQWDNGYGKIJV  XKTVWCN6JKPI EWVQXGT 6JKPI funkcja aktualizacji podczas wykonania .GVVGT ]_ `.GVVGT ]_ 6JKPI V[RG  RTQVGEVGF HTKGPFENCUU PXGNQRG FQWDNGQWPEGU UVCVKEXQKF QRGTCVQTPGY UKGAVN ] TGVWTPQRGTCVQTPGY N  _ UVCVKEXQKFQRGTCVQTFGNGVG XQKF R ] QRGTCVQTFGNGVG R  _ 5VTKPIPCOGCFFTGUU  RTKXCVG  _ Listing 9.4. Funkcje składowe klasy Letter rozwijane w miejscu ówywołania   6WVCLRQYKPP[QUVCèWOKGUEQPGYU[UVMKGQIÎNPGHWP]MELG  TQYKLCPGYOKGLUEWY[YQđCPKC,GUVVQIQFPGMQPYGP]ELæ  RQNGICLæEæPCWOKGUECPKWFGHKPKELKHWPMELKTQYKLCP[EJ]YOKGLUEW  Y[YQđCPKCRQCFGMNCTCELæMNCU[4QYKæCPKGVCMKGRQ]YCNCTÎYPKGľ  WPKMPæèE[MNKCNGľPQħEKRQOKúF[HWPMELCOKMNCU PXGNQRG]K.GVVGT   KPNKPGFQWDNG .GVVGTRQUVCIG ] KH QWPEGU TGVWTP GNUGTGVWTP  QWPEGU    _ KPNKPG6JKPI .GVVGTV[RG ] GZVGTP6JKPI NGVVGTprzykład TGVWTPNGVVGT _ Przyjmuje się, że obiekty należące do hierarchii klasy .GVVGT znajdują się zawsze wewnątrz obiektu klasy PXGNQRG i tylko ten obiekt widoczny jest dla użytkownika. Sygnatura klasy nie jest nigdy używana bezpośrednio przez użytkownika. Jednak funkcje składowe klasy .GVVGT wykorzystywane są przez interfejs klasy PXGNQRG za pośrednictwem operatora PXGNQRGQRGTCVQT . Klasa .GVVGT nie musi być konkretnym typem danych, ponieważ obsługiwana jest za pośrednictswem interfejsu klasy PXGNQRG. Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 301 Klasa .GVVGT służy jako klasa bazowa dla klas aplikacji zarządzanych przez klasę PXGNQRG. Klasa PXGNQRG zawiera składową TGR wskazującą instancję klasy .GVVGT. Rzeczywiste działanie kompozytu klas PXGNQRG i .GVVGT wykonywane jest właśnie przez obiekt jednej z klas pochodnych klasy .GVVGT. Wszystkie funkcje składowe aplikacji zostają wyspecyfikowane w interfejsie klasy listu, zwykle jako funkcje czysto wirtualne. Niektóre z funkcji, wspólne dla wszystkich klas pochodnych klasy .GVVGT mogą zostać zdefiniowane już w tej klasie. Ponieważ pozostałe funkcje zdefiniowane są jako funkcje czysto wirtualne, to mamy gwarancję, że ich implementacji dostarczą klas pochodne. Jednak w dalszej części rozdziału poka- żemy, że użycie funkcji czysto wirtualnych nie jest dopuszczalne w rozszerzonej formie idiomu wykorzystującej obiekty przykładów. Funkcje definiowane przez użytkownika powinny zwracać obiekty typów wbudowa- nych w język lub konkretnych typów danych (czyli zgodnych z ortodoksyjną postacią kanoniczną) lub typu PXGNQRG lub typu referencji PXGNQRG. W sygnaturze klasy PXGNQRG powinny pojawiać się co najwyżej stałe wskaźniki (EQPUV). Zwracanie zwy- kłych wskaźników do dynamicznie przydzielonych obszarów pamięci może bowiem naruszyć zaimplementowany schemat zarządzania pamięcią. Oczywiście funkcje defi- niowane przez użytkownika mogą być również typu XQKF. Funkcja OCMG tworzy instancję klasy pochodnej klasy .GVVGT i zwraca wskaźnik typu .GVVGT , co opisane zostało w rozdziale 8. W ogólnym przypadku istnieć może wiele przeciążonych funkcji OCMG, przy czym każda z nich zajmuje się inicjacją nowego obiektu. Żadna operacja związana z inicjacją obiektu nie powinna być pozostawiana konstruk- torowi. Na przykład funkcja .GVVGTOCMG może inicjować obiekty klas 1XGT0KIJV i (KTUV NCUU w następujący sposób: PXGNQRG .GVVGTOCMG KPVFC[UFQWDNGYGKIJV ] .GVVGT TGVXCN KH FC[UYGKIJV ] TGVXCNPGY1XGT0KIJV _GNUG] TGVXCNPGY(KTUV NCUU _ TGVXCN QWPEGUYGKIJV TGVWTP PXGNQRG TGVXCN  _ Jeśli konstruktor nie zawiera żadnej logiki związanej z inicjacją obiektów, to nie wymaga wprowadzania modyfikacji i tym samym ponownej kompilacji. Jest to istotne w środo- wisku przyrostowego ładowania kodu, w którym funkcje wirtualne (na przykład funkcje OCMG) mogą być ładowane przyrostowo, a konstruktory nie. W ogólnym przypadku klasy pochodne klasy .GVVGT nie muszą stosować ortodoksyjnej postaci kanonicznej. Chociaż powinny posiadać konstruktor domyślny oraz destruktor, to jednak specyfikacja konstruktora kopiującego oraz operatora przypisania nie jest wymagana. 302 C++. Styl i technika zaawansowanego programowania Obiekt klasy PXGNQRG może zawierać obiekt dowolnej klasy pochodnej klasy .GVVGT. Jeśli klasa PXGNQRG jest poprawnie zaprojektowana, to dowolny obiekt tej klasy może być przypisany dowolnemu innemu obiektowi tej klasy. Listing 9.5 przedstawia przy- kłady prostych klas pochodnych klasy .GVVGT. Każda z tych klas posiada własny kon- struktor domyślny. Listing 9.5. Przykładowe klasy pochodne klasy Letter ENCUU(KTUV NCUURWDNKE.GVVGT] RWDNKE (KTUV NCUU  `(KTUV NCUU   PXGNQRGOCMG   PXGNQRGOCMG FQWDNGYGKIJV   _ ENCUU1XGT0KIJVRWDNKE.GVVGT] RWDNKE 1XGT0KIJV  `1XGT0KIJV   PXGNQRGOCMG   PXGNQRGOCMG FQWDNGYGKIJV  FQWDNGRQUVCIG ]TGVWTP_  _ Zgodnie z idiomem przykładu każda klasa kopertowa posiada pojedynczy, globalnie dostępny obiekt, który wykorzystywany jest jako przykład. Obiekt ten może być two- rzony za pomocą specjalnego konstruktora w celu odróżnienia go od „zwykłych” obiektów tej klasy. Istnienie przykładów bywa często przydatne w przypadku klas listu tak, by przykład koperty mógł posiadać referencję instancji listu. Przykład listu obsługuje żądania utworzenia obiektów — wszystkie wywołania funkcji OCMG przekazywane są do obiektu listu za pośrednictwem operatora  zdefiniowanego w klasie kopertowej. Przykład listu może być specjalną instancją ogólnej klasy bazowej listu (klasy .GVVGT), jeśli nie jest ona klasą abstrakcyjną. W przeciwnym wypadku możemy utworzyć spe- cjalną klasę pochodną, która posiadać będzie domyślne definicje funkcji czysto wirtu- alnych i tworzyć pojedynczy obiekt (singleton) przykłasdu listu. Klasy posiadające symboliczną postać kanoniczną używane są w taki sam sposób jak zliczane wskaźniki i obiekty przykładów — czyli za pomocą operatora  zamiast operatora kropkowego. A oto przykład prostej aplikacji ilustrującej sposób posługi- wania się naszymi wzorcowymi klasami PXGNQRG i .GVVGT: UVCVKE PXGNQRGGPXGNQRG ZGORNCTnie jest wykorzystywany bezpośrednio PXGNQRG GPXGNQRGGPXGNQRG ZGORNCT KPVOCKP ]  PXGNQRGQXGTPKIJVGT GPXGNQRG  OCMG   QXGTPKIJVGT UGPF #FFKUQP9GUNG[4GCFKPI/#   PXGNQRGCETQUUVQYP GPXGNQRG  OCMG   QXGTPKIJVGTCETQUUVQYP CETQUUVQYP UGPF #PIYCPVKDQ$QUVQP QOOQP  TGVWTP _ Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 303 W ten sposób omówiliśmy podstawowe aspekty symbolicznej postaci kanonicznej. Aby zaakcentować i poszerzyć przedstawione dotąd motywacje, przedstawiamy poniżej zbiór zasad związanych ze stosowaniem tego idiomu: 1. Wszystkie referencje klasy PXGNQRG powinny wykorzystywać operator  , a nie zapis kropkowy. Operator  automatyzuje bowiem przekazywanie operacji do klasy .GVVGT. 2. Funkcje składowe klas listu powinny być wirtualne. Wirstualne funkcje składowe mogą być łatwo ładowane przyrostowo, co zostasnie opisane szczegółowo w dalszej części bieżącego rozdziału. 3. Funkcja składowa OCMG wykonuje zadania konstruktora. Sam konstruktor nie wykonuje żadnych operacji związanych z inicjacją obsiektów. Rozwiązanie takie jest konieczne, jeśli chcemy zachować możliwość szastępowania kodu inicjacji obiektów podczas wykonania programu, poniseważ jedynie funkcje wirtualne mogą być aktualizowane. Konstruktory są nadsal obecne w klasach pochodnych klasy 6JKPI (ich istnienie jest konieczne dla działania mechaniszmu funkcji wirtualnych), ale nie powinny zawierać żadnego kodu definiowanego przez użytkownika. 4. Każda klasa powinna posiadać pojedynczy, stały obiekt sprzykładu. Obiekt przykładu musi być łatwy do zidentyfikowania (nsa przykład na skutek utworzenia go przez specjalny konstruktor). Dostęp do przykładu nie powinien odbywać się bezpośrednio, a jedynie za pomocą wyznaczsonego w tym celu wskaźnika. Przyczyny takiego rozwiązania zostaną omówsione w dalszej części rozdziału. 5. Funkcje składowe EWVQXGT 6JKPI klas pochodnych klasy 6JKPI wykorzystywane są przez mechanizm dynamicznego ładoswania kodu podczas wykonywania programu. Parametrem tych funkcji jest wsskaźnik obiektu klasy, do której należą. Zadanie funkcji EWVQXGT polega na przekształceniu obiektu istniejącej klasy w obiekt nowej klasy różnsiącej się formatem, układem składowych i ich typem. Jeśli funkcja EWVQXGT nie potrafi tego dokonać, to może wykorzystać pewne sztuczki udostępnisane przez środowisko, aby mimo wszystko dokonać konwersji obiekstu (symulować jednokierunkową właściwość BECOMES dostępną w języku Smalltsalk). Działanie funkcji EWVQXGT zostanie omówione szczegółowo w dalszej części rozdziału. 6. Operator PGY nie może być używany dla klasy PXGNQRG, dlatego też zadeklarowany jest jako prywatny. Próba dynamicznegos utworzenia obiektu klasy PXGNQRG spowoduje błąd kompilacji. Koperty powinny być deklarsowane jedynie jako zmienne automatyczne, składowe innych kslas lub, w ostateczności, jako zmienne globalne. Wyeliminowanie wskaźników zwalsnia programistę z obowiązku usuwania nieużywanych obiektów, dzięki sczemu nawet elastyczne, polimorficzne typy w rodzaju klasy 0WODGT mogą być używane jak konkretne typy danych (czyli tym samym jak typy podsstawowe wbudowane w język, np. typ KPV). 304 C++. Styl i technika zaawansowanego programowania Idiom symboliczny wspomaga inżyniera oprogramowania, ponieważ zarządzanie pamięcią zostaje zautomatyzowane w znacznym stopniu przez klasę kopertową, która śledzi referencje obiektów. Dostęp do danych i funkcji odbywa się za pośrednictwem dodatkowej warstwy, dzięki czemu ich użytkownicy są mnie narażeni na wpływ poja- wiających się zmian. Efekt domina wywołany przez modyfikacje klasy zostaje w znacz- nym stopniu ograniczony i tym samym ponowna kompilacja kodu wymagana jest na dużo mniejszą skalę. Jeśli dysponujemy odpowiednimi narzędziami konsolidacji i łado- wania kodu, to technika taka może nawet zostać użyta do przeprowadzenia przyro- stowych modyfikacji klas działającego programu, wymagając od niego jedynie zmiany konfiguracji uwzględniającej nową klasę. Opisane rozwiązanie po raz kolejny zwiększa stopień polimorfizmu w programach tworzonych w języku C++, udostępniając typy parametryzowane podczas działania programu oraz pewien rodzaj ogólnej klasy. Idiom symboliczny stwarza iluzję, że charakterystyka typów może być zmieniana podczas wykonywania programu. Poniżej możliwość ta zostanie omówiona szczegółowo wraz z odpsowiednimi przykładami. 9.3. Przykład — ogólna klasa kolekcji Rozważmy przykład programu, który będzie używać zamiennie trzech różnych rodzajów kontenerów: opartego na tablicy i indeksach w postaci wartości całkowitych, wyko- rzystującego B-drzewa i stosującego tablice mieszające. W tym celu zdefiniujemy klasę QNNGEVKQP, która zawierać będzie wskaźnik do jednego z wymienionych obiektów wewnętrznych. Aby uzyskać dodatkową elastyczność, klasa QNNGEVKQP zdefiniowana zostanie jako szablon tak, by jego instancje mogły przechowywać obiekty dowolnego wybranego typu. Deklaracja: QNNGEVKQP$QQM#WVJQT NKDTCT[ tworzy kolekcję obiektów klasy $QQM indeksowanych za pomocą obiektów klasy #WVJQT. Obiekty listu w naszym przykładzie tworzone będą jako obiekty klas pochodnych klasy QNNGEVKQP4GR. Klasy te będą charakteryzować warianty (patrz podrozdział 7.7) klasy QNNGEVKQP4GR. W tym przypadku występować będą trzy warianty klasy QNNGEVKQP4GR — #TTC[, $VTGG i *CUJ6CDNG — służące jako alternatywne rodzaje kontenerów wyko- rzystywane przez klasę QNNGEVKQP. Większość funkcji składowych klasy QNNGEVKQP4GR i jej klas pochodnych powinna być wirtualna, dzięki czemu wywołania funkcji skła- dowych przez obiekt klasy QNNGEVKQP za pośrednictwem wskaźnika klasy QNNGEVKQP4GR będą powodować wykonanie funkcji składowych odpowiedsniej klasy #TTC[, $VTGG lub *CUJ6CDNG. Interfejs klasy QNNGEVKQP4GR musi zawierać deklaracje wszystkich funkcji składowych wymienionych klas. Klasa QNNGEVKQP dysponować będzie bowiem wyłącz- nie wiedzą o funkcjach składowych zadeklarowanych przez klasę QNNGEVKQP4GR. Ponie- waż nie wszystkie klasy pochodne będą dysponować własną implementacją wszyst- kich tych metod, to przydatnym mechanizmem może okazać się obsługa wyjątków w sytuacji, gdy wywołana zostanie nieprawidłowa funkcja składowa. Na przykład dostęp do elementów klasy #TTC[ może odbywać się jedynie za pomocą indeksu przyj- mującego wartości całkowite; tablice mieszające klasy *CUJ6CDNG mogą wykorzystywać Rozdział 9. ♦ Emulacja języków symbolicznych w C++ 305 wartości całkowite podczas wyszukiwania indeksowego lub łańcuchy znakowe podczas wyszukiwania asocjacyjnego. Jeśli więc obiekt klasy QNNGEVKQP wykorzystuje obiekt klasy #TTC[, to wywołanie QRGTCVQT=? 5 powinno spowodować wyrzucenie wyjątku. Jest to cena, jaką musimy zapłacić za elastyczność stylu programowania symbolicznego. Listing 9.6 przedstawia szkielet deklaracji klasy QNNGEVKQP4GR, która jest klasą bazową dla klas listu w naszym przykładzie. Klasy należące do hierarchii dziedziczenia klasy QNNGEVKQP4GR używają dość niezwykłego sposobu implementacji funkcji składowej V[RG. Ponieważ klasa QNNGEVKQP jest klasą parametryczną, to tworzenie na jej podstawie nowej klasy wymagać zawsze własnego przykładu (zazwyczaj administrowanego ręcznie), w skutek czego funkcja składowa V[RG nie może zwrócić po prostu globalnej wartości wskaźnika. Zamiast tego operacja OCMG danego przykładu umieszcza adres przykładu wewnątrz każdego z tworzonych obiektów (składowa GZGORNCT2QKPVGT). I właśnie tę wartość zwraca funkcja składowa V[RG. Na przykład: ENCUU NCUU GTKXGF(TQO QNNGEVKQP4GR65  RWDNKE QNNGEVKQP4GR65 ]   QNNGEVKQP65 OCMG ]  QNNGEVKQP65 PGY1DLGEV  PGY1DLGEVGZGORNCT2QKPVGTVJKU TGVWTPPGY1DLGEV _  _ 6JKPI QNNGEVKQP4GRV[RG ]TGVWTPGZGORNCT2QKPVGT]_ Klasa QNNGEVKQP4GR sama w sobie stanowi użyteczną abstrakcję i może zostać bez- pośrednio wykorzystana przez inny idiom. Logiczna hermetyzacja hierarchii klasy QNNGEVKQP4GR wewnątrz klasy QNNGEVKQP ma dwie zalety. Po pierwsze, pozwala klasie QNNGEVKQP zmieniać sposób reprezentacji kolekcji na żądanie podczas wykonywania programu. Możliwe jest także przypisanie kolekcji jednego typu kolekcji innego typu, dzięki czemu różne kolekcje mogą być praktycznie używane wymiennie. Kolekcje o znacznych rozmiarach mogą używać wartości progowych lub innych kryteriów, decydując się na zmianę swojego typu podczas działania programu w c
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

C++. Styl i technika zaawansowanego 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ą: