Poznanie ruchów figur szachowych to dopiero pierwszy krok w nauce tej gry. Aby ją opanować, trzeba zrozumieć strategie i taktyki, które wpływają na każdy ruch. To samo dotyczy języka C++. Znajomość właściwych strategii pomaga unikać pułapek i pracować o wiele skuteczniej. Rob Murray dzieląc się swoim doświadczeniem pomaga programistom C++ wykonać następny krok w kierunku tworzenia wydajnych aplikacji.
Licznie występujące w całej książce przykłady kodu mają na celu zilustrowanie przydatnych strategii programistycznych i ostrzec przed nabyciem niebezpiecznych nawyków. Aby dodatkowo ułatwić przyswajanie nowych umiejętności, każdy rozdział kończy się listą poruszonych w nim kluczowych zagadnień oraz pytaniami mającymi spowodować przemyślenia i dyskusje.
Książka przedstawia między innymi:
* Tworzenie właściwych abstrakcji dla projektu i przekształcanie abstrakcji w klasy C++
* Mechanizmy dziedziczenia pojedynczego i wielokrotnego
* Metody tworzenia klas
* Szczegółowy opis mechanizmu szablonów
* Wskazówki dotyczące stosowania wyjątków
* Metody tworzenia kodu nadającego się do wielokrotnego wykorzystania
* Przenoszenie programów z języka C do C++
Robert B. Murray jest wicedyrektorem ds. inżynierii oprogramowania w firmie Quantitative Data Systems dostarczającej niestandardowych rozwiązań z zakresu oprogramowania dla czołowych firm. [więcej...]
C++. Strategie i taktyki. Vademecum profesjonalisty
1. IDZ DO
PRZYK£ADOWY ROZDZIA£
SPIS TRE CI C++. Strategie i taktyki.
Vademecum profesjonalisty
KATALOG KSI¥¯EK
Autor: Robert B. Murray
KATALOG ONLINE T³umaczenie: Przemys³aw Steæ
ISBN: 83-7361-323-4
ZAMÓW DRUKOWANY KATALOG Tytu³ orygina³u: C++ Strategies and Tactics
Format: B5, stron: 240
TWÓJ KOSZYK
DODAJ DO KOSZYKA Poznanie ruchów figur szachowych to dopiero pierwszy krok w nauce tej gry.
Aby j¹ opanowaæ, trzeba zrozumieæ strategie i taktyki, które wp³ywaj¹ na ka¿dy ruch.
To samo dotyczy jêzyka C++. Znajomo æ w³a ciwych strategii pomaga unikaæ pu³apek
CENNIK I INFORMACJE i pracowaæ o wiele skuteczniej. Rob Murray dziel¹c siê swoim do wiadczeniem pomaga
programistom C++ wykonaæ nastêpny krok w kierunku tworzenia wydajnych aplikacji.
ZAMÓW INFORMACJE Licznie wystêpuj¹ce w ca³ej ksi¹¿ce przyk³ady kodu maj¹ na celu zilustrowanie
O NOWO CIACH przydatnych strategii programistycznych i ostrzec przed nabyciem niebezpiecznych
nawyków. Aby dodatkowo u³atwiæ przyswajanie nowych umiejêtno ci, ka¿dy rozdzia³
ZAMÓW CENNIK koñczy siê list¹ poruszonych w nim kluczowych zagadnieñ oraz pytaniami maj¹cymi
spowodowaæ przemy lenia i dyskusje.
Ksi¹¿ka przedstawia miêdzy innymi:
CZYTELNIA • Tworzenie w³a ciwych abstrakcji dla projektu i przekszta³canie abstrakcji
w klasy C++
FRAGMENTY KSI¥¯EK ONLINE • Mechanizmy dziedziczenia pojedynczego i wielokrotnego
• Metody tworzenia klas
• Szczegó³owy opis mechanizmu szablonów
• Wskazówki dotycz¹ce stosowania wyj¹tków
• Metody tworzenia kodu nadaj¹cego siê do wielokrotnego wykorzystania
• Przenoszenie programów z jêzyka C do C++
Robert B. Murray jest wicedyrektorem ds. in¿ynierii oprogramowania w firmie
Quantitative Data Systems dostarczaj¹cej niestandardowych rozwi¹zañ z zakresu
oprogramowania dla czo³owych firm. Wcze nie pracowa³ w AT&T Bell Labs, gdzie bra³
udzia³ w rozwoju jêzyka C++, jego kompilatorów i bibliotek. Jest pierwszym redaktorem
magazynu „The C++ Report”. Od 1987 prowadzi zajêcia dotycz¹ce jêzyka C++ na
Wydawnictwo Helion konferencjach naukowych i technicznych.
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
2. 5RKU VTG EK
2TGFOQYC
9RTQYCFGPKG
4QFKC #DUVTCMELC
1.1. Abstrakcja numeru telefonu............................................................................................17
1.2. Związki między abstrakcjami .........................................................................................19
1.3. Problem warunków brzegowych ....................................................................................24
1.4. Projektowanie z wykorzystaniem kart CRC ...................................................................25
1.5. W skrócie ........................................................................................................................26
1.6. Pytania ............................................................................................................................26
4QFKC -NCU[
2.1. Konstruktory ...................................................................................................................27
2.2. Przypisanie......................................................................................................................34
2.3. Dane publiczne ...............................................................................................................36
2.4. Niejawne konwersje typów.............................................................................................40
2.5. Operatory przecią one — składowe czy nie?.................................................................44
2.6. Przecią enie, argumenty domyślne i wielokropek .........................................................47
2.7. Słowo kluczowe const ....................................................................................................48
2.8. Zwracanie referencji .......................................................................................................54
2.9. Konstruktory statyczne ...................................................................................................55
2.10. W skrócie ......................................................................................................................56
2.11. Pytania ..........................................................................................................................57
4QFKC 7EJY[V[
3.1. Klasa Lancuch.................................................................................................................60
3.2. Unikanie kopiowania przez zastosowanie liczników u ycia ..........................................61
3.3. Zapobieganie powtórnym kompilacjom — „Kot z Cheshire”........................................66
3.4. Stosowanie uchwytów w celu ukrycia szczegółów projektu..........................................68
3.5. Implementacje wielokrotne.............................................................................................69
3.6. Uchwyty jako obiekty .....................................................................................................72
3.7. Podsumowanie ................................................................................................................73
3.8. W skrócie ........................................................................................................................73
3.9. Pytania ............................................................................................................................73
4QFKC KGFKEGPKG
4.1. Związek generalizacji (specjalizacji)..............................................................................75
4.2. Dziedziczenie publiczne .................................................................................................78
4.3. Dziedziczenie prywatne ..................................................................................................78
4.4. Dziedziczenie chronione.................................................................................................82
4.5. Zgodność z abstrakcjami klasy bazowej.........................................................................83
4.6. Funkcje czysto wirtualne ................................................................................................85
3. 6 C++. Strategie i taktyki. Vademecum profesjonalisty
4.7. Szczegóły i pułapki związane z dziedziczeniem ............................................................87
4.8. W skrócie ........................................................................................................................90
4.9. Pytania ............................................................................................................................90
4QFKC KGFKEGPKG YKGNQMTQVPG
5.1. Dziedziczenie wielokrotne jako iloczyn zbiorów ...........................................................91
5.2. Wirtualne klasy bazowe..................................................................................................96
5.3. Pewne szczegóły dotyczące dziedziczenia wielokrotnego .............................................99
5.4. W skrócie ......................................................................................................................101
5.5. Pytania ..........................................................................................................................101
4QFKC 2TQLGMVQYCPKG RQF MæVGO FKGFKEGPKC
6.1. Interfejs chroniony ........................................................................................................103
6.2. Czy nale y projektować pod kątem dziedziczenia?......................................................106
6.3. Projektowanie pod kątem dziedziczenia — kilka przykładów .....................................111
6.4. Podsumowanie ..............................................................................................................116
6.5. W skrócie ......................................................................................................................116
6.6. Pytania ..........................................................................................................................117
4QFKC 5CDNQP[
7.1. Szablon klasy Para ........................................................................................................119
7.2. Kilka szczegółów dotyczących szablonów...................................................................122
7.3. Konkretyzacja szablonu ................................................................................................123
7.4. Inteligentne wskaźniki ..................................................................................................125
7.5. Argumenty wyra eniowe szablonów............................................................................131
7.6. Szablony funkcji ...........................................................................................................132
7.7. W skrócie ......................................................................................................................135
7.8. Pytania ..........................................................................................................................136
4QFKC 5CDNQP[ CCYCPUQYCPG
8.1. Klasy kontenerowe wykorzystujące szablony ..............................................................139
8.2. Przykład — klasa Blok .................................................................................................141
8.3. Szczegóły projektowe klasy Blok.................................................................................143
8.4. Kontenery z iteratorami — klasa Lista .........................................................................148
8.5. Zagadnienia dotyczące projektowania iteratorów ........................................................154
8.6. Zagadnienia dotyczące wydajności ..............................................................................157
8.7. Ograniczenia dotyczące argumentów szablonów .........................................................160
8.8. Specjalizacje szablonów ...............................................................................................162
8.9. W skrócie ......................................................................................................................168
8.10. Pytania ........................................................................................................................168
4QFKC /Q NKYQ è RQPQYPGIQ Y[MQT[UVCPKC
9.1. Poznanie i nabycie ........................................................................................................172
9.2. Odporność.....................................................................................................................173
9.3. Zarządzanie pamięcią ...................................................................................................179
9.4. Alternatywne metody alokacji pamięci ........................................................................181
9.5. Przekazywanie argumentów do operatora new ............................................................184
9.6. Zarządzanie zasobami zewnętrznymi ...........................................................................187
9.7. Znajdowanie błędów pamięci .......................................................................................187
9.8. Konflikty nazw .............................................................................................................192
9.9. Wydajność ....................................................................................................................195
9.10. Nie zgaduj — zmierz!.................................................................................................195
9.11. Algorytmy ...................................................................................................................196
9.12. Wąskie gardła w dynamicznej alokacji pamięci.........................................................197
9.13. Funkcje rozwijane w miejscu wywołania ...................................................................202
4. Spis treści 7
9.14. Prawo Tiemanna .........................................................................................................204
9.15. W skrócie ....................................................................................................................204
9.16. Pytania ........................................................................................................................205
4QFKC 9[LæVMK
10.1. Sprostowanie...............................................................................................................209
10.2. Dlaczego wyjątki?.......................................................................................................209
10.3. Przykład wyjątku ........................................................................................................212
10.4. Wyjątki powinny być wyjątkowe ...............................................................................213
10.5. Zrozumieć wyjątki ......................................................................................................215
10.6. Oszacowanie winy ......................................................................................................215
10.7. Projektowanie obiektu wyjątku ..................................................................................217
10.8. W skrócie ....................................................................................................................219
10.9. Pytania ........................................................................................................................219
4QFKC 2TGPQUGPKG RTQLGMVÎY FQ %
11.1. Wybór języka C++......................................................................................................221
11.2. Przyswajanie C++ .......................................................................................................223
11.3. Projektowanie i implementacja...................................................................................224
11.4. Tworzenie bazy zasobów............................................................................................226
11.5. Uwagi końcowe ..........................................................................................................227
11.6. W skrócie ....................................................................................................................227
11.7. Pytania ........................................................................................................................228
5MQTQYKF
5. Rozdział 4.
KGFKEGPKG
Wiele dyskusji dotyczących dziedziczenia rozpoczyna się od objaśnienia reguł języka.
Chocia poznanie tych reguł jest niezbędne do korzystania z samego mechanizmu, to
najpierw powinniśmy się upewnić, e rozumiemy, gdzie w projekcie dziedziczenie po-
winno zostać zastosowane. Programy z nieodpowiednio zaprojektowanym dziedzicze-
niem mo na wprawdzie doprowadzić do kompilacji, lecz będą one trudne do zrozumienia
i utrzymania.
YKæGM IGPGTCNKCELK
URGELCNKCELK
Dziedzicznie powinno zostać zastosowane w przypadku, gdy nowa klasa (klasa po-
chodna) opisuje pewien zbiór obiektów, który jest podzbiorem obiektów opisywanych
przez klasę bazową. Zale ność ta jest związkiem generalizacji (lub specjalizacji):
ENCUU 2QLCF ]
RWDNKE
XKTVWCN FQWDNG RT[URKGU
FQWDNG
_
ENCUU 5COQNQV RWDNKE 2QLCF ]
RWDNKE
XKTVWCN FQWDNG RT[URKGU
FQWDNG
_
Ka dy obiekt typu 5COQNQV jest równie obiektem typu 2QLCF (zauwa my, e relacja
odwrotna nie jest prawdziwa — mogą istnieć obiekty typu 2QLCF, które nie są obiektami
typu 5COQNQV). Ka da operacja, którą mo na zastosować do obiektu typu 2QLCF powinna
równie mieć sens przy zastosowaniu do obiektu typu 5COQNQV (tj. funkcje składowe klasy
bazowej mogą być wywoływane dla obiektów klasy pochodnej). W klasie pochodnej
mo na zmienić implementację funkcji składowej przez przesłonięcie jej, lecz operacja
pojęciowa powinna nadal mieć sens w klasie pochodnej. Ka dy pojazd mo na przyspie-
szyć — rower będzie korzystał z innej implementacji tej operacji ni pociąg, lecz operacja
pojęciowa będzie taka sama.
KGFKEGPKG C Q GPKG
Dziedziczenie nie powinno być stosowane w przypadku, gdy klasa bazowa jest składni-
kiem obiektu opisywanego przez klasę pochodną:
6. 76 C++. Strategie i taktyki. Vademecum profesjonalisty
ENCUU 5MT[FNQ ]
RWDNKE
FQWDNG FNWIQUE
EQPUV
_
ENCUU 5COQNQV RWDNKE 5MT[FNQ ] 0KGY C EKYG FKGFKEGPKG
UEGIÎ [ RQOKPKúVQ
_
Obiekt typu 5COQNQV nie jest specjalnym rodzajem obiektu typu 5MT[FNQ, któremu
przypadkiem doczepiono kadłub — 5COQNQV jest raczej obiektem zło onym z innych
obiektów, w tym obiektu typu 5MT[FNQ (obiekt typu 5COQNQV nie jest obiektem typu
5MT[FNQ, on ma 5MT[FNQ).
Takie niewłaściwe u ycie mechanizmu dziedziczenia pozwala u ytkownikom stosować
operacje klasy 5MT[FNQ do obiektu typu 5COQNQV:
5COQNQV U
FQWDNG F UFNWIQUE
W wyniku wywołania funkcji UFNWIQUE
zostanie zwrócona długość obiektu typu 5MT[
FNQ, a nie obiektu typu 5COQNQV. Nie oznacza to wcale, e taki kod nie mo e działać, jest
on jednak mylący — dlaczego skrzydło traktowane jest inaczej ni silnik czy śmigło?
W jaki sposób skonstruować dwupłat — samolot o dwóch skrzydłach?
W przypadku, gdy obiekt składa się z innych obiektów, właściwym podejściem będzie
uczynienie tych obiektów składowymi, a nie klasami bazowymi:
ENCUU 5COQNQV ]
5MT[FNQ UM
5OKINQ UO
KVF
_
Oznacza to, e w operacjach na części 5MT[FNQ obiektu typu 5COQNQV trzeba będzie
jawnie wymieniać składową UM typu 5MT[FNQ, lecz dzięki temu związek pomiędzy kla-
sami 5COQNQV a 5MT[FNQ jest jaśniejszy — jest to związek ma (agregacji), a nie jest (ge-
neralizacji).
7UWYCPKG QRGTCELK Y MNCUKG RQEJQFPGL
Ka da operacja występująca w bardziej ogólnej klasie bazowej powinna mieć zastosowanie
do ka dego obiektu klasy pochodnej. Chocia w klasie pochodnej mo na zdefiniować
nową implementację operacji przez przesłonięcie funkcji składowej klasy bazowej, to nie
nale y próbować usunąć operacji, która jest dozwolona w klasie bazowej przez zadekla-
rowanie jej jako prywatnej.
Poni ej przedstawiamy przykład (wadliwej) hierarchii realizującej obiekty typu 2QLCF
o dwóch rodzajach prędkości — prędkości normalnej, RTGFMQUE
, mierzonej względem
podło a oraz prędkości lotu, RTGFMQUEANQVW
, mierzonej względem powietrza, która
w obecności wiatru mo na być ró na od wartości RTGFMQUE
:
7. Rozdział 4. Dziedziczenie 77
ENCUU 2QLCF ] [ RTQLGMV
RWDNKE
FQWDNG RTGFMQUE
EQPUV
FQWDNG RTGFMQUEANQVW
EQPUV
_
ENCUU 2QLCFANCFQY[ RWDNKE 2QLCF ]
RTKXCVG
2QLCF[ NæFQYG PKG RQUKCFCLæ RTúFMQ EK NQVW
FQWDNG RTGFMQUEANQVW
EQPUV
RWDNKE
UEGIÎ [ RQOKPKúVQ
_
Ponowna deklaracja składowej 2QLCFANCFQY[RTGFMQUEANQVW jako prywatnej stanowi próbę
uniemo liwienia wywołania funkcji RTGFMQUEANQVW dla obiektu typu 2QLCFANCFQY[:
ENCUU 5COQEJQF RWDNKE 2QLCFANCFQY[ ]
UEGIÎ [ RQOKPKúVQ
_
2QLCFANCFQY[
12. RR PGY 5COQEJQF
FQWDNG NQV RR RTGFMQUEANQVW
QYQNQPG
Fakt podejmowania prób ograniczenia operacji w klasach pochodnych wskazuje zazwy-
czaj na to, e projekt hierarchii klas jest błędny. Aby rozwiązać ten problem w naszym
przykładzie, musimy rozstrzygnąć, czy mówienie o składowej RTGFMQUEANQVW
pojazdu
lądowego ma w ogóle sens. Jeśli uznamy, e nie, to będzie znaczyło, e deklaracja funkcji
składowej RTGFMQUEANQVW
nie powinna występować w adnej klasie, która jest klasą
bazową dla klasy 2QLCFANCFQY[. Zamiast tego, powinna zostać przeniesiona do takiego
miejsca w hierarchii klas, gdzie pytanie o prędkość lotu postawione wobec obiektów tej
klasy i wszystkich jej klas pochodnych będzie miało zawsze sens. W naszym przykła-
dzie powinniśmy utworzyć nową klasę 2QLCFARQYKGVTP[, z której będą wyprowadzane
wszystkie pojazdy posiadające prędkość lotu:
ENCUU 2QLCF ]
RWDNKE
FQWDNG RTGFMQUE
EQPUV
_
ENCUU 2QLCFANCFQY[ RWDNKE 2QLCF ]
14. 78 C++. Strategie i taktyki. Vademecum profesjonalisty
Przy takiej hierarchii klas nie mamy mo liwości wywołania funkcji RTGFMQUEANQVW wobec
obiektu typu 2QLCFANCFQY[, nawet poprzez wskaźnik typu 2QLCF
15. (propozycję innego
sposobu rozwiązania tego problemu zawiera pytanie 1 na końcu tego rozdziału).
KGFKEGPKG RT[YCVPG EJTQPKQPG K RWDNKEPG
Klasa określa dwa interfejsy dla świata zewnętrznego — jeden dla u ytkowników (skład-
niki publiczne) oraz drugi dla implementatorów klas pochodnych (składniki chronione
i prywatne). Mechanizm dziedziczenia działa w taki sam sposób: jeśli dziedziczenie jest
publiczne, to wchodzi w skład interfejsu przeznaczonego dla u ytkowników, którzy
mogą tym samym tworzyć kod zale ny od tego dziedziczenia. Jeśli dziedziczenie jest
chronione, to jest jedynie częścią interfejsu przeznaczonego dla implementatorów klas
pochodnych. Jeśli natomiast jest prywatne, to w ogóle nie wchodzi w skład interfejsu —
mo e z niego korzystać jedynie implementator klasy (oraz klasy zaprzyjaźnione).
KGFKEGPKG RWDNKEPG
Dziedziczenie publiczne stosowane jest w przypadku, gdy dziedziczenie wchodzi w skład
interfejsu, tj. pragniemy poinformować naszych u ytkowników o fakcie, e obiekt typu
X jest obiektem typu Y (klasa X jest wyprowadzona z klasy Y). Podobnie jak w przy-
padku wszystkich pozostałych elementów interfejsu, zobowiązujemy się (do pewnego
stopnia) nigdy nie zmieniać tego elementu klasy! A to dlatego, e u ytkownicy mogą
stworzyć kod uzale niony od niejawnej konwersji wskaźnika lub referencji do klasy 2Q
EJQFPC na wskaźnik lub referencję do klasy $CQYC:
XQKF WUVCYAMQNQT
-UVCNV -QNQT
-QNQ M
MQ Q Q RTQOKGPKW
WUVCYAMQNQT
MPKGDKGUMK
Powy szy kod opiera się na fakcie, e Koło jest Kształtem, a więc referencję do Koła
M mo na przekazać do ka dej funkcji posiadającej parametr typu -UVCNV . Oznacza to,
e nie mo emy w przyszłości zmodyfikować tej klasy, usuwając z niej dziedziczenie
i oczekiwać, e istniejący ju kod będzie działał! Byłaby to niezgodna modyfikacja in-
terfejsu — równowa na usunięciu publicznej funkcji składowej.
KGFKEGPKG RT[YCVPG
Dziedziczenie prywatne stosowane jest w przypadku, gdy dziedziczenie nie stanowi ele-
mentu interfejsu, a jedynie implementacyjny szczegół. U ytkownicy nie mogą tworzyć
kodu uzale nionego od takiego dziedziczenia, dzięki czemu zachowujemy mo liwość mo-
dyfikacji implementacji polegającej na rezygnacji z u ywania danej klasy bazowej.
Dziedziczenie prywatne stosowane jest znacznie rzadziej ni dziedziczenie publiczne,
poniewa realizacja zło enia (czyli wykorzystanie części „klasy bazowej” jako danej
składowej) jest prostsza i działa zazwyczaj równie dobrze. Zamiast dziedziczenia po
klasie bazowej, pojedynczy obiekt tej klasy bazowej umieszczany jest jako składowa
w klasie (dawnej) pochodnej. Takie rozwiązanie nie powinno powodować adnej utraty
16. Rozdział 4. Dziedziczenie 79
Powtórka: Dziedziczenie publiczne, chronione i prywatne
W języku C++ istnieją trzy rodzaje dziedziczenia: RWDNKE, RTQVGEVGF oraz RTKXCVG. We wszystkich
formach dziedziczenia funkcje składowe klas pochodnych mają dostęp do składowych publicz-
nych i chronionych klasy bazowej — lecz nie do składowych prywatnych. Te trzy typy dziedziczenia
różnią się elementami, które są widoczne dla użytkownika klasy pochodnej (a nie twórcy klasy
pochodnej) oraz okolicznościami, w których użytkownik może niejawnie przekonwertować wskaź-
nik do klasy pochodnej na wskaźnik do klasy bazowej.
Najczęstszą formą dziedziczenia jest dziedziczenie publiczne:
ENCUU -UVCNV ]
RWDNKE
-QNQT MQNQT
_
MNCUC -QNQ RWDNKE -UVCNV ]
UEGIÎ [ RQOKPKúVQ
RWDNKE
-QNQ
WPUKIPGF
WPUKIPGF RTQOKGP
EQPUV
_
Przy zastosowaniu dziedziczenia publicznego, składowe publiczne klasy bazowej pozostają pu-
bliczne w klasie pochodnej, a składowe chronione klasy bazowej pozostają chronione w klasie
pochodnej:
-QNQ M
WVYÎT MQ Q Q RTQOKGPKW
MMQNQT
1- HWPMELC MQNQT
LGUV UM CFQYæ RWDNKEPæ
RWDNKEPGL MNCU[ DCQYGL
Wskaźnik do klasy pochodnej może zostać niejawnie przekonwertowany na wskaźnik do publicznej
klasy bazowej:
-UVCNV
17. MR PGY -QNQ
1- -UVCNV LGUV RWDNKEPæ
MNCUæ DCQYæ
Przy zastosowaniu dziedziczenia prywatnego, składowe publiczne i chronione klasy bazowej stają
się prywatne w klasie pochodnej. Dostęp do nich mają składowe oraz funkcje i klasy zaprzyjaź-
nione klasy pochodnej, lecz nie użytkownicy:
ENCUU .CPEWEJ ]
RWDNKE
.CPEWEJ
EQPUV EJCT
19. KPV CYKGTCAMNCYKUGAURGELCNPG
EQPUV
_
Składowe klasy 0WOGTAVGNGHQPW mogą korzystać ze składowych publicznych i chronionych klasy
.CPEWEJ:
KPENWFG EV[RGJ HWPMELC KUFKIKV
KPV
0WOGTAVGNGHQPWCYKGTCAMNCYKUGAURGELCNPG
EQPUV ]
20. 80 C++. Strategie i taktyki. Vademecum profesjonalisty
HQT
KPV K K FNWIQUE
K
KH
KUFKIKV
21. VJKU=K?
TGVWTP
TGVWTP
_
Użytkownicy klasy 0WOGTAVGNGHQPW nie mogą jednak wywoływać żadnych składowych klasy .CPEWEJ:
OCKP
]
0WOGTAVGNGHQPW PV
KPV FNWI PVFNWIQUE
$ æF MQORKNCELK
UM CFQYC FNWIQUE LGUV RT[YCVPC
Użytkownicy nie mogą również wykonać niejawnej konwersji wskaźnika do klasy pochodnej na
wskaźnik do klasy bazowej:
XQKF H
]
0WOGTAVGNGHQPW U
.CPEWEJ
22. YUM U $ æF MQORKNCELK .CPEWEJ
LGUV RT[YCVPæ MNCUæ DCQYæ
_
Przy zastosowaniu dziedziczenia prywatnego, składowe publiczne i chronione klasy bazowej stają
się chronione w klasie pochodnej. Klasy pochodne mogą wywoływać funkcje składowe chronio-
nej klasy bazowej, a także niejawnie przekonwertować wskaźnik do klasy pochodnej na wskaźnik
do chronionej klasy bazowej.
Składową publiczną prywatnej lub chronionej klasy bazowej można uczynić publiczną w klasie po-
chodnej za pomocą tzw. deklaracji dostępu:
ENCUU .CPEWEJ ]
RWDNKE
KPV FNWIQUE
EQPUV
_
ENCUU 0WOGTAVGNGHQPW RTKXCVG .CPEWEJ ]
RWDNKE
.CPEWEJFNWIQUE GMNCTCELC FQUVúRW
_
Dzięki temu użytkownicy będą mieli możliwość wywoływania dla obiektu typu 0WOGTAVGNGHQPW
funkcji FNWIQUE tak, jak gdyby została ona zadeklarowana w następujący sposób:
ENCUU 0WOGTAVGNGHQPW RTKXCVG .CPEWEJ ]
RWDNKE
KPV FNWIQUE
] TGVWTP .CPEWEJFNWIQUE
_
_
wydajności ani wymagać dodatkowego obszaru pamięci, a powstała w ten sposób klasa
będzie łatwiejsza do zrozumienia, poniewa czytając kod nie będzie trzeba pamiętać,
które funkcje składowe dziedziczone są po prywatnej klasie bazowej.
Implementacja przykładowej klasy 0WOGTAVGNGHQPW zaprezentowanej w ramce „Powtórka:
Dziedziczenie publiczne, chronione i prywatne” powinna zostać zmodyfikowana w na-
stępujący sposób:
25. KPV CYKGTCAMNCYKUGAURGELCNPG
EQPUV
_
KPV
0WOGTAVGNGHQPWCYKGTCAMNCYKUGAURGELCNPG
EQPUV ]
HQT
KPV K K UFNWIQUE
K
KH
KUFKIKV
U=K?
TGVWTP
TGVWTP
_
Jedyne zmiany w treści kodu funkcji składowych klasy 0WOGTAVGNGHQPW wynikają z ko-
nieczności uściślenia niejawnych odwołań do składowych klasy bazowej .CPEWEJ nazwą
składowej U typu .CPEWEJ. Zmiana ta jest niewidoczna dla u ytkowników — ich kod
będzie działał jak dotąd. Mało prawdopodobne jest równie , eby zmianie uległy czas
wykonania lub przestrzeń wykorzystywana przez ich programy. I w jednym, i w drugim
przypadku obiekt musi zawierać jedną kopię składnika odpowiadającego klasie .CPEWEJ.
W większości przypadków klasa nieposiadająca klas bazowych będzie łatwiejsza do zro-
zumienia i rozbudowy ni równowa na klasa wykorzystująca dziedziczenie prywatne.
Zastosowanie zło enia oznacza równie , e późniejsze dodanie nowej klasy bazowej
będzie wymagać dziedziczenia pojedynczego, a nie wielokrotnego. Na wielu platformach
kod wykorzystujący dziedziczenie wielokrotne jest zauwa alnie wolniejszy i większy
od kodu, którym zastosowano dziedziczenie pojedyncze, a ponadto jest zawsze trudniej-
szy do zrozumienia.
Wyjątek od tej reguły ma miejsce w przypadku, gdy w klasie pochodnej trzeba przesłonić
funkcję wirtualną klasy bazowej, a nie chcemy tej klasy bazowej udostępniać w pu-
blicznym interfejsie. Dziedziczenie prywatne stanowi w takiej sytuacji najprostsze, a nie-
kiedy jedyne rozwiązanie (jeśli przesłaniana funkcja wirtualna to destruktor).
Załó my, na przykład, e korzystamy ze środowiska języka C++, które obsługuje me-
chanizm tzw. zbierania nieu ytków (ang. garbage collection) w przypadku obiektów wy-
prowadzonych z klasy DKGTCNP[. Ka de wywołanie funkcji DKGTCLAPKGW[VMK
będzie
powodować usunięcie tych „zbieralnych” obiektów, do których nie mo na się odwołać
za pomocą istniejących wskaźników:
MNCUC DKGTCNP[ ]
RWDNKE
DKGTCNP[
XKTVWCN `DKGTCNP[
]_
_
27. ER
YJKNG
ER PCLFAPKGQUKCICNP[AQDKGMV
FGNGVG ER
_
Załó my ponadto, e projektujemy klasę podlegającą procesowi zbieraniu nieu ytków,
która reprezentuje węzły grafu. Chocia przed u ytkownikami nie będziemy mogli naj-
prawdopodobniej ukryć faktu, e nasz węzeł podlega zbieraniu nieu ytków, to mo emy
ukryć wybór procedury zbierania nieu ytków. Realizujemy to przez u ycie klasy DKG
TCNP[ jako prywatnej klasy bazowej:
ENCUU 9GGN RTKXCVG DKGTCNP[ ]
RWDNKE
XKTVWCN `9GGN
UEGIÎ [ RQOKPKúVQ
_
Przesłaniając destruktor wirtualny zapewniamy, e instrukcja FGNGVG ER występująca
w treści funkcji DKGTCLAPKGW[VMK w przypadku, gdy będzie dotyczyć obiektu klasy
9GGN, spowoduje wywołanie destruktora klasy 9GGN. Dzięki zastosowaniu dziedzicze-
nia prywatnego zachowujemy mo liwość zmiany implementacji klasy 9GGN polegającej
na u yciu jakiegoś innego mechanizmu zbierania nieu ytków.
KGFKEGPKG EJTQPKQPG
Dziedziczenie chronione stosowane jest w przypadku, gdy dziedziczenie wchodzi w skład
interfejsu dla klas pochodnych, lecz nie jest elementem interfejsu dla u ytkowników.
Chroniona klasa bazowa jest jak prywatna klasa bazowa, która jest znana wszystkim
klasom pochodnym:
ENCUU .CPEWEJ ]
30. _
Funkcje składowe klasy 0WOGTAOKGLUEQY[ mają dostęp do składowych publicznych i chro-
nionych części podchodzącej od klasy .CPEWEJ.
Autor osobiście nigdy nie wykorzystywał dziedziczenia chronionego i nigdy nie słyszał
tak e o jego zastosowaniu w powa nym projekcie. Wszystkie powody do niestosowania
dziedziczenia prywatnego dotyczą równie dziedziczenia chronionego — zamiast chro-
nionej klasy bazowej prościej jest zazwyczaj posiadać chronioną składową:
ENCUU .CPEWEJ ]
33. _
Taka przeróbka znacznie upraszcza hierarchię dziedziczenia. Wydajność jest taka sama,
a funkcje składowe klasy 0WOGTAOKGLUEQY[ mają wcią dostęp do części obiektu pocho-
dzącej od klasy .CPEWEJ (chocia teraz muszą odwoływać się do składowej U).
Nie oznacza to wcale, e dziedziczenie chronione nigdy się nie przydaje — jeśli skła-
dowe klasy pochodnej muszą przesłonić funkcje wirtualne występujące w (chronionej)
klasie bazowej, to dziedziczenie chronione mo e stanowić odpowiednie rozwiązanie.
Jeśli jednak mo na zastosować zło enie, to tak nale y zrobić — korzystanie z mało
znanych „zakamarków” języka (takich jak dziedziczenie chronione) sprawia, e programy
są trudniejsze do zrozumienia.
IQFPQ è CDUVTCMELCOK MNCU[ DCQYGL
W klasie pochodnej mo na przesłonić wirtualną funkcję składową klasy bazowej, dekla-
rując ją ponownie z tą samą nazwą i z taką samą listą argumentów:
ENCUU 2QLCF ]
RWDNKE
XKTVWCN XQKF RT[URKGU
FQWDNG
FQWDNG RTGFMQUE
_
ENCUU 5COQEJQF RWDNKE 2QLCF ]
RWDNKE
XKTVWCN XQKF RT[URKGU
FQWDNG
_
ENCUU 1MTGVARQFYQFP[ RWDNKE 2QLCF ]
RWDNKE
XKTVWCN XQKF RT[URKGU
FQWDNG
_
Przy dziedziczeniu istnieje jednak znacznie silniejsze ograniczenie dotyczące składowych
RT[URKGU klas 5COQEJQF oraz 1MTGVARQFYQFP[ ni samo wymaganie poprawności typów.
Funkcja składowa klasy pochodnej powinna być zgodna z modelem abstrakcyjnym klasy
bazowej. Chocia poszczególne implementacje mogą być ro ne, to ka dy obiekt klasy
wyprowadzonej z klasy 2QLCF powinien „przyspieszać tak, jak robi to 2QLCF” — co-
kolwiek miałoby to znaczyć. Jest to ograniczenie semantyczne — nie mo na go wyrazić
w języku C++, kompilator C++ nie mo e więc sprawdzić, czy zostało spełnione.
W przypadku klasy 2QLCF, model abstrakcyjny funkcji RT[URKGU mógłby określać, e
przyspieszenie pojazdu zmienia jego RTGFMQUE o określoną wartość, tj.:
przyspiesz (x ) ⇒ ( predkoscnowa == predkoscstara + x )
Gdy tylko ta część abstrakcji zostanie opisana, wszystkie klasy pochodne powinny być
z nią zgodne.
34. 84 C++. Strategie i taktyki. Vademecum profesjonalisty
Dlaczego jest to wa ne? Jeśli wszystkie klasy pochodne są zgodne z modelem abstrak-
cyjnym, u ytkownicy mogą tworzyć kod oparty na tym modelu:
XQKF
CVT[OCL
2QLCF R ]
RRT[URKGU
RRTGFMQUE
_
i kod ten będzie działać w przypadku wszystkich Pojazdów:
1MTGVARQFYQFP[ QTGN
CVT[OCL
QTGN
5COQEJQF XQNMUYCIGP
CVT[OCL
XQNMUYCIGP
W przyszłości mogą zostać dodane nowe rodzaje obiektów typu 2QLCF i będą one po-
prawnie działać z kodem, który został zaimplementowany w czasach, kiedy one jeszcze
nie istniały!
ENCUU 5COQNQV RWDNKE 2QLCF ]
RWDNKE
XKTVWCN XQKF RT[URKGU
FQWDNG
_
5COQNQV DQGKPIA
CVT[OCL
DQGKPIA /KGLO[ PCFKGLú G LGUVG O[ PC KGOK
Zaimplementowaliśmy funkcję, która, dzięki zastosowaniu wywołań kilku operacji abs-
trakcyjnych (wirtualnych funkcji składowych), działa z ka dą klasą, która jest wyprowa-
dzona z klasy 2QLCF i poprawnie realizuje te operacje abstrakcyjne, nie posiadając jedno-
cześnie adnej innej wiedzy na temat tych obiektów. To jest właśnie jedna z głównych
zalet projektowania obiektowego.
Jeśli związek pomiędzy składowymi RTGFMQUE i RT[URKGU nie będzie wyraźnie udoku-
mentowany i rozumiany przez projektantów, znajdzie się ktoś, kto zaimplementuje klasę
pochodną, która nie będzie zgodna z modelem abstrakcyjnym, np.:
ENCUU TCIUVGT RWDNKE 5COQEJQF ]
RWDNKE
XKTVWCN XQKF RT[URKGU
FQWDNG
_
XQKF
TCIUVGTRT[URKGU
FQWDNG FGNVC ]
TCIUVGT[ RT[URKGUCLæ DCTFQ U[DMQ
5COQEJQFRT[URKGU
35. FGNVC
_
Autor klasy TCIUVGT źle zrozumiał, jak powinna działać funkcja składowa RT[URKGU.
Dlatego klasa TCIUVGT nie jest zgodna z modelem abstrakcyjnym klasy 2QLCF.
Rozwa my, co się stanie, jeśli dla obiektu typu TCIUVGT poruszającego się z prędkością
100 km/h wywołamy funkcję CVT[OCL. Funkcja CVT[OCL wykona następujące wywołanie
RRT[URKGU
RRTGFMQUE
36. Rozdział 4. Dziedziczenie 85
które w tym przypadku spowoduje wywołanie funkcji
TCIUVGTRT[URKGU
co z kolei wywoła funkcję
5COQEJQFRT[URKGU
Po wywołaniu funkcji CVT[OCL nasz TCIUVGT będzie jechał z prędkością 100 km/h
w przeciwnym kierunku! Z pewnością programista nie to miał na myśli. Pomimo e kod
spełnia ograniczenia dotyczące typów narzucane przez język — kompilacja przebiega
bez problemów — to jego działanie nie jest prawidłowe, poniewa klasa TCIUVGT nie
jest zgodna z modelem abstrakcyjnym klasy 2QLCF.
(WPMELG E[UVQ YKTVWCNPG
Nasza pierwotna klasa 2QLCF zawiera deklarację funkcji składowej RT[URKGU. Umie-
ściliśmy ją w tej klasie, poniewa RT[URKGU jest operacją, która jest pojęciowo po-
prawna dla wszystkich pojazdów. Oczekujemy, e wersja tej funkcji występująca w kla-
sie bazowej zostanie przesłonięta w ka dej klasie pochodnej.
W jaki sposób powinniśmy zaimplementować funkcję 2QLCFRT[URKGU? Nie prze-
widujemy w ogóle tworzenia obiektów typu 2QLCF. Klasa 2QLCF jest za to klasą bazową,
która opisuje pojęcia wspólne dla zbioru klas pochodnych. W zamierzeniu klasa 2QLCF
ma być u ywana wyłącznie jako klasa bazowa, a funkcja RT[URKGU zostanie przesło-
nięta w ka dej klasie pochodnej. Nie spodziewamy się więc, eby ktoś kiedykolwiek
wywołał funkcję 2QLCFRT[URKGU. Jedno podejście mogłoby polegać na zdefinio-
waniu wersji, która w przypadku wywołania wyświetli komunikat o błędzie:
XQKF
2QLCFRT[URKGU
FQWDNG ]
EGTT 9[YQ CPC HWPMELC 2QLCFRT[URKGU!P
CDQTV
_
lecz takie podejście będzie wykrywać brak przesłonięcia funkcji RT[URKGU dopiero
podczas wykonywania. Lepszym rozwiązaniem będzie wykorzystanie pewnego mecha-
nizmu języka C++, który pozwoli wykryć to podczas kompilacji — przez deklarację funk-
cji 2QLCFRT[URKGU jako tzw. funkcji czysto wirtualnej.
Dzięki zadeklarowaniu klasy 2QLCF jako klasy abstrakcyjnej, kompilator będzie genero-
wać błąd kompilacji przy ka dej próbie utworzenia obiektu typu 2QLCF. Nie musimy sobie
zadawać trudu definiowania namiastek funkcji dla funkcji składowych klasy bazowej.
Z tego powodu zastosowanie funkcji czysto wirtualnych i abstrakcyjnych klas bazowych
zalecane jest w przypadku klas takich jak 2QLCF, które opisują zbiory klasy pochodnych.
Destruktor nigdy nie powinien być funkcją czysto wirtualną:
ENCUU 2QLCF ]
RWDNKE
XKTVWCN `2QLCF
[ RQO[U
UEGIÎ [ RQOKPKúVQ
_
37. 86 C++. Strategie i taktyki. Vademecum profesjonalisty
Powtórka: Funkcje czysto wirtualne i abstrakcyjne klasy bazowe
Wirtualna funkcja składowa, w której w deklaracji po liście argumentów występuje wyrażenie :
ENCUU % ]
XKTVWCN XQKF H
_
jest tzw. funkcją czysto wirtualną. Nie trzeba podawać żadnej definicji funkcji czysto wirtualnej
%H
. Każda klasa, która deklaruje lub dziedziczy funkcję czysto wirtualną jest abstrakcyjną klasą
bazową. Próba utworzenia obiektu abstrakcyjnej klasy bazowej spowoduje błąd podczas kompilacji.
Jeśli w klasie wyprowadzonej z klasy % funkcja %H zostanie przesłonięta, to ta klasa będzie już
klasą konkretną (nieabstrakcyjną):
ENCUU RWDNKE % ]
XQKF H
_
Abstrakcyjna klasa bazowa służy do deklarowania interfejsu bez deklarowania pełnego zbioru
implementacji dla tego interfejsu. Taki interfejs określa operacje abstrakcyjne realizowane przez
wszystkie obiekty wyprowadzone z tej klasy — obowiązek zapewnienia implementacji dla tych
operacji abstrakcyjnych spoczywa już na klasach pochodnych. Na przykład:
ENCUU 2QLCF ]
RWDNKE
XKTVWCN FQWDNG RT[URKGU
FQWDNG
XKTVWCN FQWDNG RTGFMQUE
_
Ponieważ klasa 2QLCF jest abstrakcyjna, próba utworzenia obiektu typu 2QLCF powoduje błąd
kompilacji:
2QLCF R $ æF MQORKNCELK
MNCUC 2QLCF LGUV CDUVTCME[LPC
Aby móc użyć klasy 2QLCF OWUKO[ dla niej utworzyć klasy pochodne:
ENCUU 5COQEJQF RWDNKE 2QLCF ]
RWDNKE
XKTVWCN FQWDNG RT[URKGU
FQWDNG
XKTVWCN FQWDNG RTGFMQUE
_
ENCUU 4QYGT RWDNKE 2QLCF ]
RWDNKE
XKTVWCN FQWDNG RT[URKGU
FQWDNG
XKTVWCN FQWDNG RTGFMQUE
_
Ponieważ w klasach 5COQEJQF oraz 4QYGT wszystkie funkcje czysto wirtualne klasy bazowej zostały
przesłonięte, możemy tworzyć obiekty obydwu klas.
Chociaż nie mogą istnieć żadne obiekty typu 2QLCF, to jednak możemy używać wskaźników i refe-
rencji do tego typu:
XQKF
CVT[OCL
2QLCF R ]
RRT[URKGU
RRTGFMQUE
_
38. Rozdział 4. Dziedziczenie 87
Klasa pochodna, która dziedziczy (nie przesłania) funkcję czysto wirtualną jest także abstrakcyjna:
ENCUU 2QLCFANCFQY[ RWDNKE 2QLCF ]
_
2QLCFANCFQY[ R $ æF MQORKNCELK
MNCUC 2QLCFANCFQY[ LGUV CDUVTCME[LPC
Destruktor 2QLCF`2QLCF będą wywoływać destruktory ka dej klasy wyprowadzonej
z klasy 2QLCF. Poniewa definicja tego destruktora musi istnieć (w przeciwnym razie otrzy-
mamy błędy modułu ładującego), deklarowanie go jako czysto wirtualnego nie ma sensu.
5EGIÎ [ K RW CRMK YKæCPG FKGFKEGPKGO
Sposób obsługi dziedziczenia przez język C++ zawiera kilka sztuczek. Przyjrzyjmy się im:
'NGOGPV[ PKGRQFNGICLæEG FKGFKEGPKW
Podczas korzystania z mechanizmu dziedziczenia nale y zawsze pamiętać o elementach,
które nie są dziedziczone po klasie bazowej:
Konstruktory (w tym konstruktor kopiujący). Jeśli nie zadeklarujemy konstruktora
kopiującego, automatycznie zostanie utworzony konstruktor kopiujący, który
będzie wywoływać konstruktory kopiujące niestatycznych danych składowych
oraz klas bazowych.
Destruktor. Jeśli nie zadeklarujemy destruktora, a dowolna z niestatycznych danych
składowych lub klas bazowych posiada destruktor, to automatycznie zostanie
utworzony destruktor, który będzie wywoływać destruktory niestatycznych danych
składowych oraz klas bazowych. Destruktor ten będzie wirtualny, jeśli dowolna
z klas bazowych posiada destruktor wirtualny.
Operator przypisania. Jeśli nie zadeklarujemy operatora przypisania, automatycznie
zostanie utworzony operator przypisania, który będzie wywoływać operatory
przypisania niestatycznych danych składowych oraz klas bazowych.
Ukryte funkcje składowe. Jeśli funkcja składowa klasy bazowej nie jest przesłonięta
w klasie pochodnej, a w tej klasie pochodnej zadeklarowana jest funkcja o tej samej
nazwie, lecz o ró nych argumentach, to funkcja występująca w klasie bazowej
będzie ukryta. Na przykład:
ENCUU 5COQEJQF ]
RWDNKE
2QQUVC æ VTG è RQOKPKúVQ
XQKF MKGTWL
KPV UVQRPKG
_
ENCUU #WVQRKNQV ]
RWDNKE
#WVQRKNQV
_
39. 88 C++. Strategie i taktyki. Vademecum profesjonalisty
ENCUU +PVGNKIGPVP[AUCOQEJQF RWDNKE 5COQEJQF ]
RWDNKE
2QQUVC æ VTG è RQOKPKúVQ
XQKF MKGTWL
#WVQRKNQV 7MT[YC HWPMELú 5COQEJQFMKGTWL
KPV
_
Nasz +PVGNKIGPVP[AUCOQEJQF mo e być kierowany za pomocą autopilota, lecz
jednocześnie ukryliśmy wersję funkcji MKGTWL występującą w klasie bazowej:
+PVGNKIGPVP[AUCOQEJQF U
UMKGTWL
$ æF MQORKNCELK PKG OQ PC WVYQT[è
QDKGMVW V[RW #WVQRKNQV V[RW KPV
Jeśli nie chcemy, aby funkcja klasy bazowej była ukryta, musimy ją ponownie
zadeklarować w klasie pochodnej:
ENCUU +PVGNKIGPVP[AUCOQEJQF RWDNKE 5COQEJQF ]
RWDNKE
2QQUVC æ VTG è RQOKPKúVQ
XQKF MKGTWL
KPV K ] 5COQEJQFMKGTWL
K _
XQKF MKGTWL
#WVQRKNQV
_
Niektóre kompilatory języka C++, w przypadku gdy funkcja zadeklarowana
w klasie pochodnej powoduje ukrycie funkcji klasy bazowej, generują ostrze enie.
5VQUQYCPKG U QYC MNWEQYGIQ XKTVWCN Y MNCUKG RQEJQFPGL
Przy przesłanianiu funkcji wirtualnej (lub czysto wirtualnej) nie trzeba jawnie określać
słowa kluczowego XKTVWCN — kompilator „zauwa y”, e dana funkcja składowa posiada
tę samą nazwę i te samy typy argumentów co funkcja wirtualna zadeklarowana w klasie
bazowej:
ENCUU $CQYC ]
RWDNKE
XKTVWCN XQKF H
_
ENCUU 2QEJQFPC RWDNKE $CQYC ]
RWDNKE
XQKF H
TÎYPQYC PG FGMNCTCELK XKTVWCN XQKF H
_
Umieszczanie słowa kluczowego XKTVWCN jest jednak dobrym zwyczajem w takim przy-
padku, poniewa dzięki niemu kod staje się bardziej oczywisty. Znaczenie programu
jest w obydwu przypadkach takie samo.
(WPMELG YKTVWCNPG
Y[YQ [YCPG RQKQOW MQPUVTWMVQTÎY K FGUVTWMVQTÎY
W przypadku gdy funkcje wirtualne wywoływane są z poziomu konstruktora lub de-
struktora obiektu, ich działanie jest nieco inne. Gdy konstruktor tworzy część bazową
klasy pochodnej, konstruowany obiekt traktowany jest tak, jakby był obiektem klasy
bazowej, a nie klasy pochodnej. Oznacza to, e wywołanie funkcji wirtualnej spowoduje
40. Rozdział 4. Dziedziczenie 89
wykonanie takiej wersji tej funkcji, która będzie odpowiednia dla klasy bazowej, której
konstruktor będzie aktualnie wykonywany, a PKG dla klasy pochodnej.
Na przykład:
ENCUU $CQYC ]
RWDNKE
$CQYC
XKTVWCN XQKF MQOWPKMCVAFKCIP
] EQWV $CQYC$CQYC
P _
_
ENCUU 2QEJQFPC RWDNKE $CQYC ]
RWDNKE
2QEJQFPC
XKTVWCN XQKF MQOWPKMCVAFKCIP
] EQWV 2QEJQFPC2QEJQFPC
P _
_
$CQYC$CQYC
]
MQOWPKMCVAFKCIP
_
OCKP
]
$CQYC D
2QEJQFPC R
_
Program ten wypisze na ekranie:
$CQYC$CQYC
$CQYC$CQYC
Wywołanie funkcji MQOWPKMCVAFKCIP w treści konstruktora klasy $CQYC będzie zawsze
powodować wywołanie składowej $CQYCMQOWPKMCVAFKCIP
, nawet jeśli konstruktor
ten tworzy część typu $CQYC obiektu typu 2QEJQFPC.
To zagadkowe zachowanie spowodowane jest faktem, e części obiektu pochodzące od
klasy bazowej konstruowane są przed jego danymi składowymi. W momencie tworzenia
części $CQYC obiektu typu 2QEJQFPC, nie istnieje jeszcze adna z danych składowych klasy
2QEJQFPC. Wywołanie wersji funkcji wirtualnej z klasy 2QEJQFPC nie miałoby więc sensu,
poniewa wersja ta próbowałby prawdopodobnie odwoływać się do niezainicjalizowanych
danych składowych klasy 2QEJQFPC (patrząc na ten problem z innej strony, mo na powie-
dzieć, e gdy wywoływany jest konstruktor klasy $CQYC, obiekt nie jest jeszcze właściwie
obiektem typu 2QEJQFPC, więc składowe klasy 2QEJQFPC nie powinny być wywoływane).
Ta sama logika ma zastosowanie wobec wywołań funkcji wirtualnych w treści destruktorów:
$CQYC`$CQYC
]
MQOWPKMCVAFKCIP
_
Ten destruktor będzie zawsze wywoływał funkcję $CQYCMQOWPKMCVAFKCIP, nawet jeśli
niszczymy część typu $CQYC obiektu typu 2QEJQFPC. W chwili wywołania destruktora klasy
$CQYC dane składowe klasy 2QEJQFPC są ju zniszczone, więc wywołanie wersji funkcji
MQOWPKMCVAFKCIP
z klasy 2QEJQFPC nie miałoby sensu.
41. 90 C++. Strategie i taktyki. Vademecum profesjonalisty
Pamiętajmy, e takie szczególne zachowanie ma miejsce tylko w przypadku, gdy funkcja
wirtualna wywoływana jest dla obiektu będącego w trakcie konstrukcji lub niszczenia.
Wywołanie funkcji wirtualnej dla jakiegoś innego obiektu będzie działać normalnie,
nawet jeśli ma miejsce w treści konstruktora czy destruktora:
$CQYC$CQYC
]
MQOWPKMCVAFKCIP
9[YQ WLG HWPMELú $CQYCMQOWPKMCVAFKCIP
$CQYC
42. DR PGY 2QEJQFPC
DR MQOWPKMCVAFKCIP
9[YQ WLG HWPMELú 2QEJQFPCMQOWPKMCVAFKCIP
_
9 UMTÎEKG
Dziedziczenie jest związkiem generalizacji (specjalizacji), czyli relacją typu jest
— obiekty implementowane przez klasę pochodną powinny reprezentować podzbiór
obiektów implementowanych przez klasę bazową.
Dziedziczenie publiczne stosujemy w przypadku, gdy dziedziczenie jest elementem
interfejsu. Dziedziczenie prywatne lub chronione stosujemy tylko wtedy, gdy
dziedziczenie stanowi ukryty szczegół implementacyjny.
W większości przypadków zamiast dziedziczenia prywatnego nale y zastosować
zło enie — wyjątkiem jest sytuacja, gdy w klasie pochodnej trzeba przesłonić
funkcję wirtualną zadeklarowaną w (prywatnej) klasie bazowej.
Funkcja wirtualna przesłonięta w klasie pochodnej powinna być zgodna z modelem
abstrakcyjnym klasy bazowej.
Konstruktory, destruktory oraz operatory przypisania nie podlegają dziedziczeniu.
2[VCPKC
Załó my, e w naszej hierarchii klas 2QLCF chcielibyśmy zdefiniować RTGFMQUEA
NQVW pojazdu lądowego jako synonim jego składowej RTGFMQUE. W jaki sposób
wpłynie to na hierarchię klas 2QLCF? Czy zmiana ta uprości, czy raczej utrudni
korzystanie z tych klas?
W jaki sposób zapewnisz, aby klasa 2QLCF potwierdzała, e ka da implementacja
funkcji RT[URKGU w klasie pochodnej jest zgodna z modelem abstrakcyjnym?
(Wskazówka: mo esz sprawić, aby klasy pochodne zamiast funkcji RT[URKGU
przesłaniały jakieś inne funkcje.) Jaki to będzie miało wpływ na czas wykonania?
Funkcję czysto wirtualną mo na (lecz nie trzeba) zdefiniować. Taką funkcję
mo na później wywołać jedynie bezpośrednio przy u yciu specyfikatora :
5COQEJQF U
U2QLCFRT[URKGU
W jaki sposób mo na by wykorzystać tę właściwość? Czy istnieje jakieś lepsze
rozwiązanie, które nie będzie wykorzystywać takiej niejasnej cechy języka?