Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00477 007326 16394952 na godz. na dobę w sumie
Nowoczesne projektowanie w C++. Uogólnione implementacje wzorców projektowych - książka
Nowoczesne projektowanie w C++. Uogólnione implementacje wzorców projektowych - książka
Autor: Liczba stron: 352
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-3301-2 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c++ - programowanie
Porównaj ceny (książka, ebook, audiobook).

Korzystaj z nowoczesnych technik w C++!

Język C++ jest obecny na rynku już niemal trzydzieści lat, a jednak nadal świetnie spełnia swoje zadania. Jest powszechnie używany, a wręcz niezastąpiony w wielu dziedzinach programowania. Wszędzie tam, gdzie potrzebna jest najwyższa wydajność oraz pełna kontrola nad zasobami i przebiegiem programu, sprawdza się wyśmienicie. Wystarczy odrobina chęci, dobry podręcznik i trochę czasu, aby wykorzystać pełną moc C++ w nowoczesnych technikach programowania.

Książkę, która Ci w tym pomoże, trzymasz właśnie w rękach. Czy znajdziesz czas i ochotę, aby zgłębić zawartą w niej wiedzę? Gwarantujemy, że warto! W trakcie lektury dowiesz się, jak zaimplementować w C++ najpopularniejsze wzorce projektowe. Dzięki nim błyskawicznie oprogramujesz typowe rozwiązania. Nauczysz się tworzyć dokładnie jedną instancję obiektu oraz zobaczysz, jak korzystać z fabryki obiektów czy inteligentnych wskaźników. Ponadto zapoznasz się z technikami projektowania klas, asercjami w trakcie kompilacji oraz uogólnionymi funktorami. Dzięki tej książce poczujesz na nowo satysfakcję z pisania programów w języku C++!

Czerp satysfakcję z korzystania z nowoczesnych technik programowania w C++!

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

Darmowy fragment publikacji:

Nowoczesne projektowanie w C++. Uogólnione implementacje wzorców projektowych Autor: Andrei Alexandrescu Tłumaczenie: Przemysław Szeremiota ISBN: 978-83-246-3301-2 Tytuł oryginału: Modern C++ Design: Generic Programming and Design Patterns Applied Format: 172×245, stron: 352 Korzystaj z nowoczesnych technik w C++! • Jak korzystać z wzorców projektowych w C++? • Jak stworzyć dokładnie jedną instancję obiektu? • Jak używać inteligentnych wskaźników? Język C++ jest obecny na rynku już niemal trzydzieści lat, a jednak nadal świetnie spełnia swoje zadania. Jest powszechnie używany, a wręcz niezastąpiony w wielu dziedzinach programowania. Wszędzie tam, gdzie potrzebna jest najwyższa wydajność oraz pełna kontrola nad zasobami i przebiegiem programu, sprawdza się wyśmienicie. Wystarczy odrobina chęci, dobry podręcznik i trochę czasu, aby wykorzystać pełną moc C++ w nowoczesnych technikach programowania. Książkę, która Ci w tym pomoże, trzymasz właśnie w rękach. Czy znajdziesz czas i ochotę, aby zgłębić zawartą w niej wiedzę? Gwarantujemy, że warto! W trakcie lektury dowiesz się, jak zaimplementować w C++ najpopularniejsze wzorce projektowe. Dzięki nim błyskawicznie oprogramujesz typowe rozwiązania. Nauczysz się tworzyć dokładnie jedną instancję obiektu oraz zobaczysz, jak korzystać z fabryki obiektów czy inteligentnych wskaźników. Ponadto zapoznasz się z technikami projektowania klas, asercjami w trakcie kompilacji oraz uogólnionymi funktorami. Dzięki tej książce poczujesz na nowo satysfakcję z pisania programów w języku C++! • Projektowanie klas • Asercje czasu kompilacji • Listy typów • Alokowanie małych obiektów • Funktory uogólnione • Inteligentne wskaźniki • Fabryka obiektów i fabryka abstrakcyjna • Tworzenie dokładnie jednego obiektu – wzorzec singleton • Multimetody Czerp satysfakcję z korzystania z nowoczesnych technik programowania w C++! Idź do • Spis treści • Przykładowy rozdział • Skorowidz Katalog książek • Katalog online • Zamów drukowany katalog Twój koszyk • Dodaj do koszyka Cennik i informacje • Zamów informacje o nowościach • Zamów cennik Czytelnia • Fragmenty książek online Kontakt Helion SA ul. Kościuszki 1c 44-100 Gliwice tel. 32 230 98 63 e-mail: helion@helion.pl © Helion 1991–2011 Spis treĞci Przedmowy .....................................................................................................9 WstĊp ...........................................................................................................13 PodziĊkowania .............................................................................................19 I Techniki ..............................................................................................................21 1. Klasy konfigurowane wytycznymi ..............................................................23 1.1. Projektowanie oprogramowania — klĊska urodzaju? ...................................................................23 1.2. PoraĪka interfejsu „wszechstronnego” .........................................................................................24 1.3. Ratunek w wielodziedziczeniu? ....................................................................................................26 1.4. Zalety szablonów ..........................................................................................................................26 1.5. Wytyczne i klasy wytycznych ......................................................................................................28 1.6. Wytyczne rozszerzone ..................................................................................................................32 1.7. Destruktory klas wytycznych .......................................................................................................33 1.8. Elementy opcjonalne w konkretyzacji czĊĞciowej ........................................................................34 1.9. àączenie klas wytycznych ............................................................................................................35 1.10. Klasy wytycznych a konfigurowanie struktury ...........................................................................37 1.11. Wytyczne zgodne i niezgodne ....................................................................................................38 1.12. Dekompozycja klas na wytyczne ................................................................................................40 1.13. Podsumowanie ............................................................................................................................42 2. Techniki .......................................................................................................43 2.1. Asercje statyczne ..........................................................................................................................44 2.2. CzĊĞciowa specjalizacja szablonu ................................................................................................46 2.3. Klasy lokalne ................................................................................................................................48 2.4. Mapowanie staáych na typy ..........................................................................................................49 4 SPIS TREĝCI 2.5. Odwzorowanie typu na typ ...........................................................................................................52 2.6. Wybór typu ...................................................................................................................................53 2.7. Statyczne wykrywanie dziedziczenia i moĪliwoĞci konwersji ......................................................55 2.8. TypeInfo .......................................................................................................................................58 2.9. NullType i EmptyType .................................................................................................................60 2.10. Cechy typów ...............................................................................................................................61 2.11. Podsumowanie ............................................................................................................................68 3. Listy typów ..................................................................................................71 3.1. Listy typów — do czego? .............................................................................................................71 3.2. Definiowanie list typów ................................................................................................................73 3.3. Liniowe tworzenie list typów .......................................................................................................75 3.4. Obliczanie dáugoĞci listy ..............................................................................................................76 3.5. Przygrywka ...................................................................................................................................77 3.6. DostĊp swobodny (indeksowany) .................................................................................................77 3.7. Przeszukiwanie list typów ............................................................................................................79 3.8. Dopisywanie do listy typów .........................................................................................................80 3.9. Usuwanie typu z listy ...................................................................................................................81 3.10. Usuwanie duplikatów .................................................................................................................82 3.11. ZastĊpowanie elementu na liĞcie typów .....................................................................................83 3.12. CzĊĞciowe porządkowanie listy typów .......................................................................................84 3.13. Generowanie klas z list typów ....................................................................................................87 3.14. Podsumowanie ............................................................................................................................97 3.15. Listy typów — na skróty ............................................................................................................97 4. Przydziaá pamiĊci dla niewielkich obiektów .............................................101 4.1. DomyĞlny alokator pamiĊci dynamicznej ...................................................................................102 4.2. Zasada dziaáania alokatora pamiĊci ............................................................................................102 4.3. Alokator maáych obiektów .........................................................................................................104 4.4. Chunk .........................................................................................................................................105 4.5. Alokator przydziaáów o staáym rozmiarze ..................................................................................108 4.6. Klasa SmallObjAlocator .............................................................................................................112 4.7. 3 x Tak ........................................................................................................................................113 4.8. Proste, skomplikowane, a potem znów proste ............................................................................116 4.9. Konfigurowanie alokatora ..........................................................................................................117 4.10. Podsumowanie ..........................................................................................................................118 4.11. Alokator maáych obiektów — na skróty ...................................................................................119 II Komponenty .....................................................................................................121 5. Uogólnione obiekty funkcyjne ...................................................................123 5.1. Wzorzec Command ....................................................................................................................124 5.2. Wzorzec Command w praktyce ..................................................................................................126 SPIS TREĝCI 5 5.3. Byty funkcyjne w C++ ...............................................................................................................127 5.4. Szkielet szablonu klasy Functor .................................................................................................129 5.5. Delegowanie wywoáania Functor::operator() .............................................................................133 5.6. Funktory .....................................................................................................................................135 5.7. Raz a dobrze ...............................................................................................................................137 5.8. Konwersje typów argumentów i wartoĞci zwracanej ..................................................................139 5.9. WskaĨniki do metod ...................................................................................................................140 5.10. Wiązanie argumentów wywoáania ............................................................................................144 5.11. ĩądania skomasowane ..............................................................................................................147 5.12. Z Īycia wziĊte (1) — duĪy koszt delegacji funkcji ...................................................................147 5.13. Z Īycia wziĊte (2) — alokacje na stercie ..................................................................................149 5.14. Szablon Functor w implementacji cofnij-powtórz ....................................................................150 5.15. Podsumowanie ..........................................................................................................................151 5.16. Functor — na skróty .................................................................................................................152 6. Singletony ..................................................................................................155 6.1. Statyczne dane i statyczne funkcje nie czynią singletona ...........................................................156 6.2. Podstawowe idiomy C++ dla singletonów .................................................................................157 6.3. Wymuszanie unikalnoĞci ............................................................................................................158 6.4. Usuwanie singletona ...................................................................................................................159 6.5. Problem martwych referencji .....................................................................................................162 6.6. Problem martwych referencji (1) — singleton à la feniks ..........................................................164 6.7. Problem martwych referencji (2) — sterowanie ĪywotnoĞcią ....................................................167 6.8. Implementowanie singletonów z ĪywotnoĞcią ...........................................................................169 6.9. ĩycie w wielu wątkach ...............................................................................................................173 6.10. Podsumowanie doĞwiadczeĔ ....................................................................................................176 6.11. Stosowanie szablonu SingletonHolder .....................................................................................181 6.12. Podsumowanie ..........................................................................................................................183 6.13. Szablon klasy SingletonHolder — na skróty ............................................................................183 7. Inteligentne wskaĨniki ...............................................................................185 7.1. Elementarz inteligentnych wskaĨników .....................................................................................186 7.2. Oferta ..........................................................................................................................................187 7.3. Przydziaá obiektów inteligentnych wskaĨników .........................................................................188 7.4. Metody inteligentnego wskaĨnika ..............................................................................................190 7.5. Strategie zarządzania posiadaniem .............................................................................................191 7.6. Operator pobrania adresu ............................................................................................................199 7.7. Niejawna konwersja na typ goáego wskaĨnika ...........................................................................200 7.8. RównoĞü i róĪnoĞü ......................................................................................................................202 7.9. Porównania porządkujące ...........................................................................................................207 7.10. Kontrola i raportowanie o báĊdach ............................................................................................210 7.11. WskaĨniki niemodyfikowalne i wskaĨniki do wartoĞci niemodyfikowalnych .........................211 7.12. Tablice ......................................................................................................................................212 7.13. Inteligentne wskaĨniki a wielowątkowoĞü ................................................................................213 7.14. Podsumowanie doĞwiadczeĔ ....................................................................................................217 7.15. Podsumowanie ..........................................................................................................................223 7.16. WskaĨniki SmartPtr — na skróty .............................................................................................224 6 SPIS TREĝCI 8. Wytwórnie obiektów ..................................................................................225 8.1. PrzydatnoĞü wytwórni obiektów .................................................................................................226 8.2. Wytwórnie obiektów w C++ — klasy a obiekty .........................................................................228 8.3. Implementowanie wytwórni obiektów .......................................................................................229 8.4. Identyfikatory typu .....................................................................................................................234 8.5. Uogólnienie ................................................................................................................................235 8.6. Sprostowania ..............................................................................................................................239 8.7. Wytwórnie klonów .....................................................................................................................240 8.8. Stosowanie wytwórni obiektów z innymi komponentami uogólnionymi ...................................243 8.9. Podsumowanie ............................................................................................................................244 8.10. Szablon klasy Factory — na skróty ..........................................................................................244 8.11. Szablon klasy CloneFactory — na skróty .................................................................................245 9. Wzorzec Abstract Factory ..........................................................................247 9.1. Rola wytwórni abstrakcyjnej w architekturze .............................................................................247 9.2. Interfejs uogólnionej wytwórni abstrakcyjnej .............................................................................251 9.3. Implementacja AbstractFactory ..................................................................................................254 9.4. Implementacja wytwórni abstrakcyjnej z prototypowaniem .......................................................256 9.5. Podsumowanie ............................................................................................................................261 9.6. Szablony AbstractFactory i ConcreteFactory — na skróty .........................................................262 10. Wzorzec Visitor .........................................................................................265 10.1. Elementarz modelu wizytacji ...................................................................................................265 10.2. PrzeciąĪenia i metoda wychwytująca .......................................................................................271 10.3. Wizytacja acykliczna ................................................................................................................273 10.4. Uogólniona implementacja wzorca Visitor ...............................................................................278 10.5. Powrót do wizytacji „cyklicznej” .............................................................................................284 10.6. Wariacje ....................................................................................................................................287 10.7. Podsumowanie ..........................................................................................................................290 10.8. Uogólnione komponenty wzorca wizytacji — na skróty ..........................................................290 11. Wielometody ..............................................................................................293 11.1. Czym są wielometody? .............................................................................................................294 11.2. PrzydatnoĞü wielometod ...........................................................................................................295 11.3. Podwójne przeáączanie — metoda siáowa ................................................................................296 11.4. Metoda siáowa w wersji automatyzowanej ...............................................................................298 11.5. Symetria rozprowadzania metodą siáową .................................................................................303 11.6. Rozprowadzanie logarytmiczne ................................................................................................307 11.7. Symetria w FnDispatcher .........................................................................................................312 11.8. Podwójne rozprowadzanie do funktorów .................................................................................313 11.9. Konwertowanie argumentów — statyczne czy dynamiczne? ...................................................316 11.10. Wielometody o staáym czasie rozprowadzania .......................................................................321 11.11. BasicDispatcher i BasicFastDispatcher w roli wytycznych ....................................................324 SPIS TREĝCI 7 11.12. Naprzód ..................................................................................................................................325 11.13. Podsumowanie ........................................................................................................................327 11.14. Podwójne rozprowadzanie — na skróty .................................................................................328 A Minimalistyczna biblioteka wielowątkowa ...............................................333 A.1. Krytyka wielowątkowoĞci .........................................................................................................334 A.2. PodejĞcie à la Loki .....................................................................................................................335 A.3. Operacje niepodzielne na typach caákowitoliczbowych ............................................................335 A.4. Muteksy .....................................................................................................................................337 A.5. Semantyka blokowania w programowaniu obiektowym ...........................................................339 A.6. Opcjonalny modyfikator volatile ...............................................................................................341 A.7. Semafory, zdarzenia i inne .........................................................................................................342 A.8. Podsumowanie ...........................................................................................................................342 Bibliografia ................................................................................................343 Skorowidz ..................................................................................................345 2 Techniki Ten rozdziaá bĊdzie prezentowaá zbiór technik C++, które bĊdą stosowane w dalszej czĊĞci ksiąĪki. Są to techniki pomocne w rozmaitych kontekstach, a jako takie najczĊĞciej bardzo ogólne i uĪy- teczne uniwersalnie, co pozwala znajdowaü dla nich zastosowania równieĪ w innych dziedzinach. Niektóre z tych technik, jak czĊĞciowa specjalizacja szablonu, to mechanizmy wbudowane w jĊzyk; inne, jak asercje statyczne (asercje czasu kompilacji), wymagają dodatkowego kodu obsáugującego. W rozdziale przedstawione zostaną nastĊpujące techniki (tudzieĪ narzĊdzia): x Asercje statyczne. x CzĊĞciowa specjalizacja szablonu. x Klasy lokalne. x Odwzorowania typ-wartoĞü (szablony klas Int2Type i Type2Type). x Szablon klasy Select — narzĊdzie do wyboru typu w czasie kompilacji, zaleĪnie od wartoĞci warunku logicznego. x Statyczne (w czasie kompilacji) wykrywanie dziedziczenia i moĪliwoĞci konwersji. x TypeInfo (porĊczny dodatek do std::type_info). x Traits — kolekcja cech typów stosowalnych do dowolnych typów C++. Rozpatrywana z osobna, kaĪda z tych technik (oraz kod ją realizujący) sprawia wraĪenie try- wialnej; zazwyczaj skáada siĊ na nią piĊü do dziesiĊciu wierszy áatwego do ogarniĊcia kodu. Ale wszystkie one mają istotną cechĊ: są technikami „nieterminalnymi”, co naleĪy rozumieü tak, Īe dają siĊ áączyü z innymi technikami, czego wynikiem są idiomy wyĪszego poziomu. Razem stanowią zaĞ solidny fundament usáug wspomagających budowanie efektywnych struktur architekturalnych projektu. Omówienia technik nie są nadto suche, bo uzupeániają je przykáady. ZawartoĞü rozdziaáu pole- cam równieĪ do referencji przy dalszej lekturze ksiąĪki, gdzie pojawią siĊ odniesienia do poszcze- gólnych technik i ich konglomeratów. 44 2. TECHNIKI 2.1. Asercje statyczne Od kiedy zaczĊto w C++ programowaü w sposób uogólniony, pojawiáa siĊ równieĪ potrzeba lep- szej statycznej kontroli (i lepszych, konfigurowalnych komunikatów o báĊdach), to znaczy kontroli realizowanej w czasie kompilacji. ZaáóĪmy dla przykáadu, Īe pracujemy nad funkcją do bezpiecznego rzutowania typów. Chcemy wykonaü rzutowanie jednego typu na inny przy zapewnieniu zachowania kompletu informacji: chcemy zapobiec rzutowaniu typów wiĊkszych na mniejsze. template class To, class From To safe_reinterpret_cast(From from) { assert(sizeof(From) = sizeof(To)); return reinterpret_cast To (from); } FunkcjĊ tĊ wywoáuje siĊ przy uĪyciu skáadni identycznej jak dla zwyczajnego rzutowania w C++: int i = ...; char *p = safe_reinterpret_case char* (i); Argument szablonu To okreĞla siĊ jawnie, a typ From kompilator wysnuwa sam na podstawie typu argumentu wywoáania, czyli na podstawie typu zmiennej i. Asercja naáoĪona na porównanie typów pozwala wymusiü, aby typ docelowy pomieĞciá caáoĞü informacji przechowywanej w typie Ĩródáo- wym. DziĊki temu zabezpieczeniu powyĪszy kod albo wykona skuteczną konwersjĊ typu1, albo sprowokuje niespeánioną asercjĊ w czasie wykonania. Z oczywistych wzglĊdów wolelibyĞmy wykrywaü tego rodzaju báĊdy juĪ w czasie kompila- cji. PrzecieĪ feralne rzutowanie moĪe na przykáad zostaü uĪyte w rzadko wykonywanej ĞcieĪce przebiegu programu i niekoniecznie musi siĊ ujawniü w testach. Dalej, przy przenoszeniu aplikacji na nową platformĊ albo do nowego kompilatora trudno zapamiĊtaü wszystkie potencjalne aspekty nieprzenoĞnoĞci programu; tak czy inaczej, báąd rzutowania moĪe zagnieĨdziü siĊ w kodzie na dáuĪszy czas i spowodowaü krach programu dopiero duĪo póĨniej, najpewniej na oczach klienta. Jest nadzieja: wyraĪenie obliczane w ramach asercji jest staáą czasu kompilacji, co oznacza, Īe potencjalnie jej kontrolĊ mógáby wykonaü kompilator, a nie Ğrodowisko wykonawcze. Pomysá polega na przekazaniu do kompilatora konstrukcji jĊzyka C++ tak uĪytej, aby byáa dozwolona dla wyraĪeĔ niezerowych i niedozwolona dla wyraĪeĔ, których obliczona statycznie wartoĞü wynosi zero. W ten sposób, jeĞli w kodzie pojawi siĊ wyraĪenie o wartoĞci zerowej, kompilator zasygnali- zuje báąd czasu kompilacji. Najprostszym mechanizmem asercji statycznych, dziaáającym równieĪ w jĊzyku C, jest mecha- nizm oparty na zakazie deklarowania tablicy o zerowej liczbie elementów (Van Horn 1997): #define STATIC_CHECK(expr) { char unnamed[(expr) ? 1 : 0]; } Teraz, jeĞli napiszemy: template class To, class From To safe_reinterpret_cast(From from) Ğci — przyp. autora. 1 Poprawną na wiĊkszoĞci komputerów — z reinterpret_cast nigdy nie moĪna mieü absolutnej pewno- 2.1. ASERCJE STATYCZNE { STATIC_CHCEK(sizeof(From) = sizeof(To)); return reinterpret_cast To (from); } ... void* somePointer = ...; char c = safe_reinterpret_case char (somePointer); 45 i jeĞli w danym systemie wskaĨniki są wiĊksze niĪ znaki, kompilator zaprotestuje przeciwko utwo- rzeniu tablicy o zerowej liczbie elementów, protestując tym samym przeciwko niepoĪądanej przez nas konwersji. Problem w tym, Īe otrzymany wtedy komunikat o báĊdzie nie bĊdzie ani sáowem wspominaá o próbie konwersji; najprawdopodobniej bĊdzie informowaá o niemoĪnoĞci utworzenia tablicy o zerowym rozmiarze. Nijak z tego nie wynika, Īe rozmiar typu char jest mniejszy od rozmiaru typu wskaĨnikowego. UdostĊpnienie wáasnego komunikatu o báĊdzie jest trudne, zwáaszcza jeĞli ma to byü mechanizm przenoĞny. Komunikaty o báĊdach nie podlegają Īadnym reguáom; steruje nimi wyáącz- nie kompilator. Na przykáad w przypadku niezdefiniowanej zmiennej kompilator nie ma nawet obo- wiązku podawaü nazwy tej zmiennej w komunikacie o báĊdzie. Lepszym rozwiązaniem jest siĊgniĊcie po szablon o odpowiednio sugestywnej nazwie; przy odrobinie szczĊĞcia kompilator wymieni nazwĊ szablonu w komunikacie o báĊdzie. template bool struct CompileTimeError; template struct CompileTimeError true {}; #define STATIC_CHECK(expr) (CompileTimeError (expr) != 0 (); CompileTimeError to szablon z parametrem pozatypowym (parametryzowany staáą typu bool). Szablon jest zdefiniowany wyáącznie dla wartoĞci true tej staáej. JeĞli spróbujemy skonkretyzowaü szablon CompileTimeError dla wartoĞci false, kompilator bĊdzie zmuszony do zgáoszenia báĊdu z komunikatem o niezdefiniowanej specjalizacji CompileTimeError false . Taki komunikat jest juĪ duĪo lepszą wskazówką co do przyczyny báĊdu. Nie jest to, rzecz jasna, rozwiązanie juĪ doskonaáe. Co z treĞcią komunikatu o báĊdzie? MoĪna by przekazywaü do STATIC_CHECK dodatkowy parametr i jakoĞ wymuszaü ujawnienie go w komuni- kacie o báĊdzie. SĊk w tym, Īe przekazany komunikat o báĊdzie musi byü dozwolonym identyfi- katorem C++ (bez znaków odstĊpu, niezaczynającym siĊ od cyfry itd.). Ten tok myĞlenia prowadzi nas do ulepszonej wersji szablonu CompileTimeError, widocznej poniĪej. W sumie nazwa Compile ´TimeError przestaje wtedy byü odpowiednio sugestywna; za chwilĊ okaĪe siĊ, Īe znacznie lepszą nazwą jest CompileTimeChecker. template bool struct CompileTimeChecker { CompileTimeChecker(...); }; template struct CompileTimeChecker false { }; #define STATIC_CHECK(expr, msg) { class ERROR_##msg; (void)sizeof(CompileTimeChecker (expr) != 0 ((ERROR_##msg()))); } 46 2. TECHNIKI ZaáóĪmy, Īe sizeof(char) sizeof(void*) (standard nie gwarantuje prawdziwoĞci takiej relacji w kaĪdym przypadku). Zobaczmy, co siĊ stanie, kiedy napiszemy: template class To, class From To safe_reinterpret_cast(From from) { STATIC_CHECK(sizeof(From) = sizeof(To), Destination_Type_Too_Narrow); return reinterpret_cast To (from); } ... void* somePointer = ...; char c = safe_reinterpret_cast char (somePointer); Po rozwiniĊciu makrodefinicji STATIC_CHECK kod funkcji safe_reinterpret_cast bĊdzie prezentowaá siĊ nastĊpująco: template class To, class From To safe_reinterpret_cast(From from) { { class ERROR_Destination_Type_Too_Narrow {}; (void)sizeof(( CompileTimeChecker sizeof(From) = sizeof(To) ( ERROR_Destination_Type_Too_Narrow()))); } return reinterpret_cast To (from); } PowyĪszy kod definiuje klasĊ lokalną o nazwie ERROR_Destination_Type_Too_Narrow, o pustym ciele. NastĊpnie tworzy tymczasową wartoĞü typu CompileTimeChecker sizeof(From) = sizeof ´(To) , inicjalizowaną wartoĞcią tymczasową typu ERROR_Destination_Type_Too_Narrow. Na koniec operator sizeof oblicza rozmiar wynikowej wartoĞci tymczasowej. Gdzie jest trik? OtóĪ specjalizacja CompileTimeChecker true posiada konstruktor akceptu- jący dowolne argumenty (lista parametrów w postaci wielokropka). Oznacza to, Īe kiedy spraw- dzane wyraĪenie jest obliczane jako wartoĞü true, wynikowy program jest poprawny. Natomiast kiedy porównanie pomiĊdzy typami da wartoĞü false, dojdzie do báĊdu kompilacji: kompilator nie bĊdzie mógá znaleĨü konwersji z typu ERROR_Destination_Type_Too_Narrow na typ CompileTime ´Checker false . A najlepsze, Īe porządny kompilator wypisze wtedy caákiem dokáadny komu- nikat o báĊdzie, w rodzaju: „Nie moĪna skonwertowaü ERROR_Destination_Type_Too_Narrow na CompileTimeChecker false . JesteĞmy w domu! 2.2. CzĊĞciowa specjalizacja szablonu CzĊĞciowa specjalizacja szablonu pozwala na specjalizowanie szablonu klasy dla podzbioru kon- kretyzacji moĪliwych dla tego szablonu. Przypomnijmy na razie peáną specjalizacjĊ szablonu. OtóĪ posiadając szablon klasy Widget: 2.2. CZĉĝCIOWA SPECJALIZACJA SZABLONU template class Window, class Controller class Widget { … uogólniona implementacja klasy Widget … }; 47 moĪemy jawnie specjalizowaü szablon klasy Widget dla wybranego zestawu typów, na przykáad: template class Widget ModalDialog, MyController { … specjalizowana implementacja klasy Widget … }; gdzie ModalDialog i MyController to klasy definiowane przez aplikacjĊ. Kiedy kompilator napotyka w kodzie definicjĊ specjalizacji szablonu Widget, wykorzystuje tĊ specjalizowaną implementacjĊ wszĊdzie tam, gdzie definiujemy obiekt typu Widget ModalDialog, MyController , a dla wszelkich innych konkretyzacji szablonu uĪywa definicji ogólnej Widget. Niekiedy jednak chcemy specjalizowaü Widget dla dowolnych wartoĞci parametru Window w poáączeniu z typem MyController. Potrzebujemy wtedy czĊĞciowej specjalizacji szablonu: // czĊĞciowa specjalizacja szablonu Widget template class Window class Widget Window, MyCOntroller { … czĊĞciowo specjalizowana implementacja klasy Widget … }; W czĊĞciowej specjalizacji szablonu klasy dookreĞlamy zazwyczaj tylko niektóre z wyma- ganych parametrów szablonu, a resztĊ pozostawiamy w ujĊciu ogólnym. Przy konkretyzowaniu szablonu klasy w programie kompilator próbuje dopasowaü najlepszą wersjĊ szablonu. Algorytm dopasowania jest bardzo zawiáy i Ğcisáy, co pozwala na doĞü nietypowe zastosowania czĊĞciowych specjalizacji. Na przykáad zaáóĪmy, Īe posiadamy szablon klasy Button parametryzowany jednym parametrem. Wtedy, nawet jeĞli wyspecjalizowaliĞmy juĪ Widget dla dowolnego parametru typo- wego Window i klasy MyController, moĪemy dalej specjalizowaü szablon Widget dla wszystkich konkretyzacji szablonu Button w poáączeniu z typem MyController: template class ButtonArg class Widget Button ButtonArg , MyCOntroller { … dalsza specjalizacja szablonu klasy Widget … }; Jak widaü, moĪliwoĞci czĊĞciowej specjalizacji szablonów są doĞü niezwykáe. Przy konkrety- zowaniu szablonu kompilator przeprowadza dopasowanie wzorca istniejących czĊĞciowych i peá- nych specjalizacji w poszukiwaniu najlepszego dopasowania; w ten sposób zyskujemy niebagatelną elastycznoĞü. Niestety, czĊĞciowa specjalizacja szablonów nie dotyczy funkcji — czy to samodzielnych, czy metod szablonów klas — co poniekąd redukuje elastycznoĞü i precyzjĊ specjalizacji: 48 2. TECHNIKI x ChociaĪ moĪna caákowicie specjalizowaü metody szablonu klasy, nie moĪna czĊĞciowo spe- cjalizowaü metod. x Nie moĪna czĊĞciowo specjalizowaü szablonów funkcji o zasiĊgu przestrzeni nazw. NajbliĪsze modelowi czĊĞciowej specjalizacji funkcji jest przeciąĪanie. W ujĊciu praktycznym oznacza to, Īe precyzyjna specjalizacja dotyczy jedynie parametrów wywoáania funkcji, ale nie moĪe juĪ dotyczyü wartoĞci zwracanej ani typów wykorzystywanych wewnĊtrznie. Na przykáad: template class T, class U T Fun(U obj); // szablon gáówny template class U void Fun void, U (U obj); // niedozwolona specjalizacja czĊĞciowa template class T T Fun (Window obj); // dozwolona specjalizacja (przeciąĪenie) Brak moĪliwoĞci precyzyjnego czĊĞciowego specjalizowania funkcji uáatwia pracĊ programistom kompilatorów, ale dla programistów aplikacji ma same sáabe strony. Niektóre z narzĊdzi prezento- wanych w dalszym omówieniu (na przykáad Int2Type i Type2Type) powstaáy wyáącznie jako odpo- wiedĨ na tĊ niedogodnoĞü. W niniejszej ksiąĪce czĊĞciowe specjalizacje szablonów wykorzystywane są bardzo szeroko. ChociaĪby caáy rozdziaá 3. omawiający mechanizm list typów jest zbudowany w oparciu wáaĞnie o specjalizacje czĊĞciowe. 2.3. Klasy lokalne Klasy lokalne są ciekawe jako maáo znana cecha jĊzyka C++. OtóĪ klasĊ moĪna zdefiniowaü wewnątrz funkcji, jak poniĪej: vid Fun() { class Local { … skáadowe danych… … definicje metod … }; … kod odwoáujący siĊ do klasy Local … } Są pewne ograniczenia: klasy lokalne nie mogą definiowaü skáadowych statycznych i nie mają dostĊpu do niestatycznych zmiennych lokalnych funkcji. Klasy lokalne są interesujące gáów- nie z tego powodu, Īe moĪna ich uĪywaü w funkcjach szablonowych. Klasy lokalne definiowane we wnĊtrzu szablonu funkcji mogą korzystaü z parametrów szablonu funkcji. PoniĪszy szablon funkcji MakeAdapter przystosowuje jeden interfejs do wymogów innego interfejsu. MakeAdapter implementuje interfejs w locie, za pomocą klasy lokalnej. Klasa lokalna przechowuje skáadowe uogólnionych typów. class Interface { public: virtual void Fun() = 0; ... }; 49 2.4. MAPOWANIE STAàYCH NA TYPY template class T, class P Interface MakeAdapter(const T obj, const P arg) { class Local : public Interface { public: Local(const T obj, const P arg) : obj_(obj), arg_(arg) {} virtual void Fun() { obj_.Call(arg_); } private: T obj_; P arg_; }; return new Local(obj, arg); } MoĪna áatwo udowodniü, Īe kaĪdy idiom uĪywający klasy lokalnej moĪna zaimplementowaü z uĪyciem szablonu klasy poza funkcją. Innymi sáowy, klasy lokalne nie są mechanizmem umoĪli- wiającym konstrukcje unikalne. Z drugiej strony, klasy lokalne mogą uproĞciü implementowanie i poprawiają lokalizacjĊ (ograniczenie zasiĊgu) symboli. Klasy lokalne posiadają jednak pewną cechĊ unikatową: są klasami finalnymi. UĪytkownicy zewnĊtrzni nie mogą wyprowadzaü pochodnych klasy ukrytej wewnątrz funkcji. Bez klas lokalnych podobny efekt wymagaáby dodania nienazwanej przestrzeni nazw w osobnej jednostce kompilacji. Klasy lokalne wykorzystamy w rozdziale 11. do utworzenia funkcji trampolinowych. 2.4. Mapowanie staáych na typy Prosty szablon opisany pierwotnie w (Alexandrescu 2000b) moĪe byü wielce pomocny przy wielu idiomach programowania uogólnionego. Oto on: template int v struct Int2Type { enum { value = v }; }; Szablon Int2Type definiuje odrĊbny typ dla kaĪdej przekazanej odrĊbnej staáej wartoĞci caákowitej. To dlatego, Īe odrĊbne konkretyzacje szablonów stanowią peánoprawne odrĊbne typy; z tego wzglĊdu Int2Type 0 to typ odmienny niĪ Int2Type 1 itd. Dodatkowo wartoĞü generująca typ jest „utrwa- lana” w skáadowej wyliczeniowej value. Szablonu Int2Type moĪna uĪywaü wszĊdzie tam, gdzie chcemy szybko „otypowaü” staáe wartoĞci caákowite. W ten sposób moĪna zrealizowaü rozprowadzanie wywoáaĔ do róĪnych funkcji, zaleĪnie od wyniku wyraĪenia obliczanego w czasie kompilacji. Efektywnie moĪna w ten sposób zrealizowaü statyczne rozprowadzanie wywoáaĔ na bazie staáych wartoĞci caákowitych. Szablon w rodzaju Int2Type jest zazwyczaj stosowany, kiedy speánione są poniĪsze warunki: 50 2. TECHNIKI x Zachodzi potrzeba wywoáania jednej z wielu wersji funkcji, zaleĪnie od staáej znanej w czasie kompilacji. x Zachodzi potrzeba rozprowadzenia tego wywoáania w czasie kompilacji. W przypadku rozprowadzania w czasie wykonania moĪna uciec siĊ do prostej konstrukcji kaskadowych instrukcji warunkowych if-else albo instrukcji wyboru switch. Niekiedy jednak jest to niepoĪądane. Instrukcje if-else wymagają poprawnego skompilowania obu alternatywnych gaáĊzi kodu, nawet jeĞli wartoĞü warunku znana jest juĪ w czasie kompilacji. Niejasne? Za chwilĊ siĊ wyjaĞni. WeĨmy taki przypadek: zaprojektowaliĞmy uogólniony kontener NiftyContainer, parametry- zowany typem przechowywanych obiektów: template class T class NiftyContainer { ... }; Powiedzmy, Īe NiftyContainer przechowuje wskaĨniki do obiektów typu T. Aby powieliü obiekt zawarty w NiftyContainer, trzeba albo wywoáaü jego konstruktor kopiujący (dla typów niepolimorficznych), albo wywoáaü metodĊ wirtualną Clone() (dla typów polimorficznych). Infor- macjĊ o trybie powielania uzyskujemy od uĪytkownika za poĞrednictwem parametru szablonu w postaci staáej logicznej: template typename T, bool IsPolymorphic class NiftyContainer { ... void DoSomething() { T* pSomeObj = ...; if (isPolymorphic) { T* pNewObj = pSomeObj- Clone(); … algorytm dla typów polimorficznych … } else { T* pNewObj = new T(*pSobeObj); … algorytm dla typów monomorficznych … } } }; SĊk w tym, Īe kompilator nie przepuĞci takiego kodu. PoniewaĪ w wersji polimorficznej algo- rytm wykorzystuje metodĊ pObj- Clone(), wywoáanie NiftyContainer::DoSomething() nie skom- piluje siĊ dla Īadnego typu, który nie definiuje metody Clone(). Prawda, Īe w czasie kompilacji jest tu oczywiste, którą gaáąĨ instrukcji warunkowej program faktycznie wykona, ale dla kompi- latora nie jest to argumentem — równieĪ kod w nieuĪywanej gaáĊzi musi byü poprawny, choüby miaá byü póĨniej wytrzebiony z programu w ramach optymalizacji i usuwania „martwego” kodu. 2.4. MAPOWANIE STAàYCH NA TYPY 51 Próba wywoáania DoSomething dla typu NiftyContainer int,false nieodwoáalnie zatrzyma kompilacjĊ na wierszu wywoáania pObj- Clone(). Báąd kompilacji moĪe pojawiü siĊ zresztą równieĪ w drugiej gaáĊzi kodu, dla wersji niepo- limorficznej, w której nastĊpuje próba utworzenia obiektu za pomocą wywoáania new T(*pObj). Báąd ten wystąpi, kiedy typ T nie bĊdzie udostĊpniaá konstruktora kopiującego (na przykáad przez oznaczenie konstruktora jako prywatnego) — co zresztą w przypadku typów polimorficznych jest zalecane. Byáoby znacznie lepiej, gdyby kompilator nie próbowaá nawet kompilowaü kodu, który i tak siĊ nie wykona; jak to osiągnąü? Okazuje siĊ, Īe istnieje kilka rozwiązaĔ tego problemu, a wĞród nich Int2Type jest jednym z bardziej eleganckich. Transformuje on ad hoc wartoĞü typu logicznego IsPolymorphic na dwa róĪne typy, reprezentujące wartoĞci true i false. Wystarczy uĪyü Int2Type IsPolymorphic z pro- stym przeciąĪaniem: template typename T, bool isPolymorphic class NiftyContainer { private: void DoSomething(T* pObj, Int2Type true ) { T* pNewObj = pObj- Clone(); … algorytm polimorficzny … } void DoSomething(T* pObj, Int2Type false ) { T* pNewObj = new T(*pObj); … algorytm niepolimorficzny … } public: void DoSomething(T* pObj) { DoSomething(pObj, Int2Type isPolymorphic ()); } }; Int2Type jako mechanizm konwersji wartoĞci na typ jest bardzo porĊczny. Wystarczy prze- kazaü wartoĞü tymczasową uzyskanego typu do przeciąĪonej funkcji. PrzeciąĪenie wybiera wtedy poĪądany wariant algorytmu. Sztuczka dziaáa, poniewaĪ kompilator nie kompiluje funkcji szablonowych, które nie są uĪy- wane — sprawdza jedynie ich poprawnoĞü skáadniową. A w kodzie szablonowym rozprowadza- nie wywoáaĔ zazwyczaj odbywa siĊ statycznie (w czasie kompilacji). NarzĊdzie Int2Type moĪna obejrzeü w dziaáaniu w kilku miejscach w bibliotece Loki, a takĪe w rozdziale 11. przy omawianiu wielometod. Mamy tam szablon klasy w roli mechanizmu podwój- nego rozprowadzania, z parametrem typu logicznego okreĞlającym opcjĊ rozprowadzania syme- trycznego. 52 2. TECHNIKI 2.5. Odwzorowanie typu na typ W podrozdziale 2.2 padáo stwierdzenie, Īe nie moĪna czĊĞciowo specjalizowaü funkcji szablo- nowych. Niekiedy jednak zachodzi potrzeba zasymulowania takiej moĪliwoĞci. WeĨmy poniĪszą funkcjĊ: template class T, class U T* Create(const U arg) { return new T(arg); } Metoda Create tworzy nowy obiekt, przekazując argument wywoáania do konstruktora obiektu. ZaáóĪmy teraz, Īe w aplikacji przyjĊliĞmy reguáĊ: obiekty typu Widget są nietykalne jako kod zastany (nie mamy wpáywu na ich implementacjĊ), a ich konstruktor przyjmuje dwa argumenty wywoáania, z których drugi jest staáą wartoĞcią, na przykáad -1. Z kolei nasze wáasne klasy, wypro- wadzone z typu Widget, są pozbawione tego udziwnienia. Jak moĪna wyspecjalizowaü metodĊ Create tak, Īeby traktowaáa klasĊ Widget inaczej niĪ wszystkie inne typy? Oczywistym rozwiązaniem byáoby utworzenie osobnej funkcji CreateWidget, specjalnie dla tego przypadku szczególnego. Niestety, w ten sposób pozbĊdziemy siĊ jednolitego interfejsu tworzenia obiektów typu Widget i obiektów pochodnych wzglĊdem tego typu. To z kolei zniweczy uĪytecznoĞü metody Create w kodzie uogólnionym. Nie moĪemy czĊĞciowo specjalizowaü funkcji, to znaczy nie wolno nam napisaü czegoĞ takiego: // niedozwolony kod - nie próbujcie tego w domu template class U Widget* Create Widget, U (const U arg) { return new Widget(arg, -1); } Pod nieobecnoĞü czĊĞciowego specjalizowania funkcji uciekamy siĊ do jedynego dostĊpnego narzĊdzia: przeciąĪania. Rozwiązaniem byáoby przekazanie do Create atrapowego obiektu typu T i odwoáanie siĊ do przeciąĪania: template class T, class U T* Create(const U arg, T /* atrapa */) { return new T(arg); } template class U Widget* Create(const U arg, Widget /* atrapa */) { return new Widget(arg, -1); } Rozwiązanie to wprowadza narzut związany z tworzeniem obiektów (o nieokreĞlonej záoĪonoĞci), które pozostają nieuĪywane. Potrzebujemy wiĊc moĪliwie skromnego Ğrodka do przeniesienia informacji o typie T do metody Create. Tu do akcji wkroczy Type2Type: jest to reprezentant typu, jego tani identyfikator, który moĪna przekazaü do przeciąĪonej funkcji. 2.6. WYBÓR TYPU Oto definicja szablonu Type2Type: template typename T struct Type2Type { typedef T OriginalType; }; 53 Type2Type to klasa pozbawiona jakiejkolwiek „fizycznej” skáadowej, ale parametryzowana róĪnymi typami daje róĪne konkretyzacje — dokáadnie tego nam potrzeba. Teraz moĪemy napisaü tak: // implementacja metody Create na bazie przeciąĪania i Type2Type template class T, class U T* Create(const U arg, Type2Type T ) { return new T(arg); } template class U Widget* Create(const U arg, Type2Type Widget ) { return new Widget(arg, -1); } // uĪycie metody Create() String* pStr = Create( Ahoj , Type2Type String ()); Widget* pW = Create(100, Type2Type Widget ()); Drugi parametr wywoáania Create sáuĪy jedynie do wybrania odpowiedniego przeciąĪenia. Teraz moĪemy specjalizowaü Create dla róĪnych konkretyzacji Type2Type, reprezentujących róĪne typy wystĊpujące w aplikacji (i o róĪnych wymaganiach wzglĊdem skáadni kreacji obiektów). 2.6. Wybór typu Niekiedy kod uogólniony musi wybraü któryĞ z typów, zaleĪnie od wartoĞci staáej typu logicznego. ZaáóĪmy, Īe w kontenerze NiftyContainer, omawianym w podrozdziale 2.4, chcemy w roli magazynu obiektów uĪyü kontenera std::vector. Rzecz jasna, typów polimorficznych nie moĪna przechowywaü przez wartoĞü, wiĊc w magazynie umieĞcimy wskaĨniki. Z drugiej strony, typy monomorficzne jak najbardziej nadają siĊ do przechowywania przez wartoĞü i niekiedy moĪe to byü bardziej efektywne. W przykáadowym szablonie klasy: template typename T, bool isPolymorphic class NiftyContainer { ... }; 54 2. TECHNIKI chcemy przechowywaü albo vector T* (kiedy IsPolymorphic ma wartoĞü true), albo vector T (dla IsPolymorphic równego false). Zasadniczo potrzebujemy wiĊc definicji typu, na przykáad ValueType, która w zaleĪnoĞci od wartoĞci IsPolymorphic bĊdzie reprezentowaáa T* albo T. MoĪna w tym celu uĪyü szablonu cechy (ang. traits) (Alexandrescu 2000a), jak tutaj: template typename T, bool isPolymorphic struct NiftyContainerValueTraits { typedef T* ValueType; }; template typename T struct NiftyContainerValueTraits T, false { typedef T ValueType; }; template typename T, bool isPolymorphic class NiftyContainer { ... typedef NiftyContainerValueTraits T, isPolymorphic Traits; typedef typename Traits::ValueType ValueType; }; Ten sposób jest jednak niepotrzebnie zawiáy. Co wiĊcej, niespecjalnie dobrze siĊ skaluje: dla kaĪdego wyboru typu potrzebujemy nowego, osobnego szablonu cechy. Szablon klasy Select udostĊpniony w bibliotece Loki oferuje znacznie prostszy mechanizm wyboru typów. Jego definicja wykorzystuje czĊĞciową specjalizacjĊ szablonu: template bool flag, typename T, typename U struct Select { typedef T Result; }; template typename T, typename U struct Select false, T, U { typedef U Result; }; Sposób dziaáania tej specjalizacji moĪna opisaü tak: jeĞli flag ma wartoĞü true, kompilator uĪywa pierwszej (uogólnionej) definicji, wiĊc Result bĊdzie siĊ równaü T. Z kolei jeĞli flag ma wartoĞü false, do gry wkracza specjalizacja szablonu i definicja Result jest rozwijana jako U. Z takim oprzyrządowaniem definicja NiftyContainer::ValueType jest znacznie áatwiejsza: template typename T, bool isPolymorphic class NiftyContainer { ... typedef typename Select isPolymorphic, T*, T ::Result 2.7. STATYCZNE WYKRYWANIE DZIEDZICZENIA I MOĩLIWOĝCI KONWERSJI 55 ValueType; ... }; 2.7. Statyczne wykrywanie dziedziczenia i moĪliwoĞci konwersji Przy implementowaniu funkcji i klas szablonowych niejednokrotnie powstaje wątpliwoĞü: czy dla dwóch arbitralnych typów T i U, o których twórcy szablonu nic nie wiadomo, moĪna wykryü relacjĊ dziedziczenia pomiĊdzy U i T? Wykrycie takiej relacji w czasie kompilacji to klucz do imple- mentowania zaawansowanych optymalizacji w bibliotekach uogólnionych. W uogólnionej funkcji moĪna na przykáad wykorzystaü zoptymalizowany algorytm, o ile uĪywana w niej klasa implemen- tuje pewien okreĞlony interfejs. Wykrycie obecnoĞci tego interfejsu w czasie kompilacji oznacza unikniĊcie rzutowania dynamic_cast, które w czasie wykonania jest doĞü kosztowne. Wykrywanie dziedziczenia odbywa siĊ na bazie bardziej ogólnego mechanizmu, to znaczy wykrywania moĪliwoĞci konwersji. Ów ogólniejszy problem moĪna sformuáowaü tak: jak wykryü, czy dowolny typ T obsáuguje automatyczną konwersjĊ na dowolny typ U? Istnieje rozwiązanie tego problemu oparte na operatorze sizeof. UĪytecznoĞü sizeof jest doprawdy zadziwiająca: moĪna go zastosowaü wobec dowolnie záoĪonego wyraĪenia, a operator zwróci rozmiar tego wyraĪenia bez obliczania go w czasie wykonania — wszystko w czasie kom- pilacji! Oznacza to, Īe operator sizeof uwzglĊdnia przeciąĪanie, konkretyzacjĊ szablonów, reguáy konwersji — wszystko, co moĪe uczestniczyü w poprawnym wyraĪeniu jĊzyka C++. W rzeczy samej sizeof zawiera w sobie kompletne oprzyrządowanie do dedukcji typu wyraĪenia; ostatecznie przecieĪ sizeof ignoruje wartoĞü wyraĪenia, a zwrócony rozmiar to rozmiar typu wyraĪenia2. Pomysá na wykrywanie moĪliwoĞci konwersji polega na uĪyciu operatora sizeof w poáą- czeniu z funkcjami przeciąĪonymi. UdostĊpnimy dwa przeciąĪenia funkcji: jedno przyjmujące typ docelowy konwersji (U) i drugie przyjmujące argument dowolnego innego typu. MoĪemy wtedy wywoáaü funkcjĊ przeciąĪoną z argumentem w postaci tymczasowej wartoĞci typu T, którego zgodnoĞü z U chcemy sprawdziü. JeĞli w ramach rozstrzygania przeciąĪenia dojdzie do wywoáa- nia funkcji przyjmującej argument typu U, wiadomo, Īe typ T jest zgodny z typem U; jeĞli wywoáane zostanie drugie przeciąĪenie, wiadomo, Īe T nie da siĊ skonwertowaü na U. Aby wykryü funkcjĊ wywoáaną w ramach tego sprawdzianu, zaaranĪujemy dwa przeciąĪenia tak, aby ich typy zwracane byáy róĪnych rozmiarów — wnioskowanie moĪna bĊdzie wtedy oprzeü siĊ na operatorze sizeof odniesionym do wartoĞci zwracanej przeciąĪenia. Dokáadne typy przeciąĪenia są nieistotne, byleby ich rozmiar byá róĪny. Utwórzmy najpierw dwa typy o róĪnych rozmiarach (to nie takie oczywiste, bo na przykáad choü przewaĪnie char i long double są róĪnych rozmiarów, to standard tego nie gwarantuje). Naj- prostsze byáoby coĞ takiego: typedef char Small; class Big { char dummy[2]; }; 2 Istnieje propozycja uzupeánienia C++ o operator typeof, to znaczy operator zwracający typ wyraĪenia. ObecnoĞü operatora typeof ogromnie uproĞciáaby duĪą czĊĞü kodu szablonowego, czyniąc go bardziej zro- zumiaáym. GNU C++ implementuje juĪ taki operator w ramach wáasnego rozszerzenia jĊzyka. Jasne jest, Īe typeof dzieliáby z operatorem sizeof znaczną czĊĞü jego wewnĊtrznej implementacji — sizeof przecieĪ juĪ teraz musi jakoĞ okreĞlaü typ wyraĪenia — przyp. autora. 56 2. TECHNIKI Z definicji sizeof(Small) wynosi 1. Rozmiar typu Big nie jest dokáadnie znany, ale na pewno bĊdzie wiĊkszy od 1 — to wszystko, czego nam trzeba do rozróĪnienia typów. Teraz para przeciąĪeĔ. Jedno z nich ma przyjmowaü U i zwracaü, powiedzmy, obiekt typu Small: Small Test(U); Ale jak napisaü drugą funkcjĊ, która przyjmuje „dowolny inny typ”? Szablon nie jest rozwią- zaniem, poniewaĪ przy rozstrzyganiu przeciąĪenia szablon zawsze bĊdzie kwalifikowany jako „naj- lepsze dopasowanie”, co zakryje konwersjĊ. Potrzebujemy czegoĞ, co jest „gorsze” (w sensie jakoĞci dopasowania) od konwersji automatycznej — to znaczy potrzeba nam takiej konwersji, która jest uruchamiana jedynie przy braku konwersji automatycznej. Szybki przegląd reguá konwersji typów argumentów wywoáania funkcji pozwoli wytypowaü wielokropek, który jest dopasowaniem naj- gorszym z moĪliwych — znajduje siĊ na samym koĔcu listy rozpatrywanych konwersji. Dokáadnie tego szukaliĞmy: Big Test(...); (Przekazywanie obiektu C++ do funkcji z wielokropkiem prowokuje niezdefiniowany wynik, ale to nie jest istotne; nie bĊdziemy faktycznie wywoáywaü funkcji; ba, funkcja nie jest nawet zaim- plementowana — pamiĊtajmy, Īe sizeof nie oblicza wartoĞci wyraĪenia, jedynie jego typ). Teraz zastosujemy operator sizeof do próbnego wywoáania funkcji Test, przekazując do niej obiekt typu T: const bool convExists = sizeof(Test(T())) == sizeof(Small); I juĪ! Funkcja Test otrzymuje w wywoáaniu obiekt skonstruowany domyĞlnie (T()), a nastĊpnie operator sizeof() ustala rozmiar wyniku takiego wyraĪenia. Wynik moĪe mieü albo rozmiar sizeof ´(Small), albo sizeof(Big), zaleĪnie od tego, czy typ T posiada moĪliwoĞü automatycznej kon- wersji na typ U, czy nie. Zostaá tylko jeden problem: otóĪ jeĞli T posiada prywatny konstruktor domyĞlny, nie uda siĊ skompilowaü wyraĪenia T(), a wiĊc i caáego naszego sprawdzianu konwersji. Na szczĊĞcie to równieĪ moĪna obejĞü — wystarczy uĪyü funkcji-atrapy z typem wartoĞci zwracanej T (pamiĊ- tajmy, wciąĪ operujemy w kontekĞcie operatora sizeof, to znaczy bez faktycznego obliczania wartoĞci wyraĪenia — a wiĊc i bez wywoáywania funkcji, tworzenia obiektów itd.) Kompilator nie bĊdzie miaá wtedy powodów do narzekania: T MakeT(); // bez implementacji const bool convExists = sizeof(Test(MakeT())) == sizeof(Small); Nawiasem mówiąc, czy to nie cudowne, Īe tyle moĪna zdziaáaü za pomocą prostych funkcji, jak MakeT czy Test, które nie tylko nic nie robią, ale wrĊcz nie istnieją? Skoro mamy gotowy mechanizm, upakujmy caáoĞü w szablon klasy, który ukryje wszystkie szczegóáy wnioskowania o typach i udostĊpni jedynie wynik sprawdzianu moĪliwoĞci konwersji. template class T, class U class Conversion { typedef char Small; class Big { char dummy[2]; }; static Small Test( const U ); 57 2.7. STATYCZNE WYKRYWANIE DZIEDZICZENIA I MOĩLIWOĝCI KONWERSJI static Big Test(...); static T MakeT(); public: enum { exists = sizeof(Test(MakeT())) == sizeof(Small) }; }; KlasĊ sprawdzianu konwersji moĪna juĪ przetestowaü: int main() { using namespace std; cout Conversion double, int ::exists Conversion char, char* ::exists Conversion size_t, vector int ::exists ; } Ten krótki program wypisuje 1 0 0. ZauwaĪmy, Īe chociaĪ std::vector implementuje kon- struktor przyjmujący argument typu size_t, to test konwersji prawidáowo zwraca 0, poniewaĪ konstruktor ten jest wyáącznie jawny. W szablonie Conversion moĪemy zaimplementowaü jeszcze jedną staáą: sameType, która bĊdzie miaáa wartoĞü true, kiedy T i U reprezentują ten sam typ: template class T, class U class Conversion { … jak powyĪej … enum { sameType = false }; }; Implementacja sameType bĊdzie oparta na czĊĞciowej specjalizacji szablonu Conversion: template class T class Conversion T, T { public: enum { exists = 1, sameType = 1 }; }; JesteĞmy w domu. Za pomocą szablonu Conversion moĪemy teraz bardzo áatwo wykrywaü relacjĊ dziedziczenia pomiĊdzy typami: #define SUPERSUBCLASS(T, U) (Conversion const U*, const T* ::exists !Conversion const T*, const void* ::sameType) JeĞli U dziedziczy publicznie po typie T, ewentualnie jeĞli T i U są w istocie tymi samymi typami, makrodefinicja SUPERSUBCLASS(T, U) jest rozwijana do wartoĞci true. Makrodefinicja opiera siĊ na próbie ustalenia moĪliwoĞci konwersji z const U* na const T*. Taka konwersja dla arbitralnych typów U i T jest moĪliwa tylko w trzech przypadkach: 58 (1) T jest tego samego typu co U. (2) T jest publiczną klasą bazową U. (3) T jest typu void. 2. TECHNIKI Ostatni z tych przypadków jest eliminowany przez drugi test w wyraĪeniu. W praktyce pierwszy przypadek (T jest tego samego typu co U) akceptujemy jako zdegenerowaną relacjĊ dziedziczenia, poniewaĪ dla celów praktycznych moĪemy przecieĪ uznaü, Īe kaĪda klasa jest w jakimĞ sensie swoją wáasną klasą bazową. Tam, gdzie potrzebny jest silniejszy sprawdzian dziedziczenia, moĪna go zdefiniowaü nastĊpująco: #define SUPERSUBCLASS_STRICT(T, U) (SUPERSUBCLASS(T, U) !Conversion const T*, const U* ::sameType) Po co dodaliĞmy w kodzie modyfikatory const? OtóĪ chcemy zapobiec nieudanym spraw- dzianom w zawsze problematycznych przypadkach typów const. W szablonie dodanie drugiego const do typu juĪ opatrzonego modyfikatorem const jest i tak ignorowane. W skrócie, umiesz- czając w makrodefinicji SUPERSUBCLASS modyfikator const, ustawiamy siĊ zawsze po bezpiecznej stronie, niczego nie ryzykując. Dlaczego SUPERSUBCLASS, a nie BASE_OF albo po prostu INHERITS? Z bardzo praktycznego powodu. Pierwotnie w bibliotece Loki stosowana byáa makrodefinicja INHERITS, ale zapis INHE ´RITS(T, U) zawsze prowokowaá pytania o kierunek sprawdzianu — sprawdzamy, czy T dziedziczy po U, czy odwrotnie? Zapis SUPERSUBCLASS (klasa bazowa-klasa pochodna) mówi znacznie wiĊcej o tym, jak interpretowaliĞmy argumenty makrodefinicji. 2.8. TypeInfo Standard jĊzyka C++ udostĊpnia klasĊ std::type_info, która daje moĪliwoĞü analizowania typu obiektów w czasie wykonania. KlasĊ tĊ wykorzystuje siĊ zazwyczaj w poáączeniu z operatorem typeid. Operator typeid zwraca referencjĊ do obiektu type_info odpowiedniego dla operandu: void Fun(Base* pObj) { // porównanie dwóch obiektów type_info odpowiadających // faktycznym typom *pObj i Derived if (typeid(*pObj) == typeid(Derived)) { … hm, pObj wskazuje tak naprawdĊ obiekt klasy Derived … } ... } Klasa type_info oprócz operatorów porównania operator== i operator!= udostĊpnia jeszcze dwie metody: x MetodĊ name, zwracającą tekstową reprezentacjĊ typu w postaci ciągu znaków const char*. Nie ma gwarancji co do sposobu odwzorowania nazw klas na ciągi znaków, wiĊc nie naleĪy oczekiwaü, Īe typeid(Widget).name() kaĪdorazowo zwróci ciąg Widget . Zgodna ze standar- 2.8. TYPEINFO 59 dem (choü raczej nie wzorcowa) byáaby wiĊc nawet taka implementacja, która dla kaĪdego typu zwraca ciąg pusty. x MetodĊ before, wprowadzającą relacjĊ porządkowania dla obiektów type_info. Za pomocą type_info::before moĪna przeprowadziü indeksowanie po obiektach type_info. Niestety, caákiem uĪyteczne wáaĞciwoĞci klasy type_info są opakowane w sposób niepotrzeb- nie utrudniający ich uĪycie. Klasa type_info blokuje konstruktor kopiujący i operator przypisa- nia, co uniemoĪliwia przechowywanie obiektów type_info — moĪemy jedynie przechowywaü wskaĨniki. Obiekty zwracane przez typeid mają przydziaá statyczny, wiĊc nie trzeba siĊ martwiü o kontrolĊ zasiĊgu i czas Īycia. Trzeba za to siĊ troszczyü o jednoznacznoĞü wskaĨników. Standard nie gwarantuje, Īe kaĪde wywoáanie na przykáad typeid(int) zwróci referencjĊ do tego samego egzemplarza type_info. Nie moĪna wiĊc bezpoĞrednio porównywaü wskaĨników do obiek- tów type_info. NaleĪaáoby raczej przechowywaü wskaĨniki obiektów type_info i nastĊpnie porów- nywaü je za poĞrednictwem operatora type_info::operator== z wyáuskanymi wskaĨnikami. GdybyĞmy chcieli posortowaü obiekty type_info, znów powinniĞmy przechowaü wskaĨniki do type_info, a takĪe uĪyü ich metod before. W efekcie, chcąc uĪyü obiektów type_info w porząd- kujących kontenerach biblioteki STL, bĊdziemy zmuszeni do napisania prostego funktora operu- jącego na wskaĨnikach. Wszystko to jest na tyle uciąĪliwe, Īe warto napisaü klasĊ ujmującą type_info i udostĊpniającą wskaĨnik do type_info, a takĪe posiadającą: x Komplet metod klasy type_info. x SemantykĊ wartoĞci (a wiĊc publiczny konstruktor kopiujący i publiczny operator przypisania). x Proste porównania na bazie operatorów operator i operator==. Loki definiuje taką otoczkĊ w postaci klasy TypeInfo. Klasa ta prezentuje siĊ nastĊpująco: class TypeInfo { public: // konstruktory/destruktory TypeInfo(); // potrzebny dla kontenerów TypeInfo(const std::type_info ); TypeInfo(const TypeInfo ); TypeInfo operator=(const TypeInfo ); // metody zgodnoĞci bool before(const TypeInfo ) const; const char* name() const; private: const std::type_info* pInfo_; }; // operatory porównaĔ bool operator==(const TypeInfo , const TypeInfo ); bool operator!=(const TypeInfo , const TypeInfo ); bool operator (const TypeInfo , const TypeInfo ); bool operator =(const TypeInfo , const TypeInfo ); bool operator (const TypeInfo , const TypeInfo ); bool operator =(const TypeInfo , const TypeInfo ); 60 2. TECHNIKI ObecnoĞü konstruktora konwertującego przyjmującego argument typu std::type_info umoĪ- liwia bezpoĞrednie porównywanie obiektów TypeInfo z obiektami std::type_info: void Fun(Base* pObj) { TypeInfo info = typeid(Derived); ... if (typeid(*pObj) == info) { … hm, pObj wskazuje tak naprawdĊ obiekt klasy Derived … } ... } MoĪliwoĞü kopiowania i porównywania obiektów TypeInfo jest istotna w wielu przypadkach. Przykáady skutecznego uĪycia tych obiektów znajdziemy w rozdziale 8. (w wytwórni klonów) oraz w rozdziale 11. (w mechanizmie podwójnego rozprowadzania). 2.9. NullType i EmptyType Biblioteka Loki definiuje dwa bardzo proste typy: NullType i EmptyType. MoĪna je wykorzystywaü w obliczeniach na typach w celu wyróĪnienia przypadków granicznych. NullType jest klasą udostĊpniającą typom znacznik braku typu: class NullType {}; Nikt raczej nie bĊdzie tworzyá obiektów tej klasy — jej zadaniem jest jedynie sygnalizowanie: „Nie jestem interesuj
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Nowoczesne projektowanie w C++. Uogólnione implementacje wzorców projektowych
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ą: