C++. Strategie i taktyki. Vademecum profesjonalisty

20
Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: [email protected] PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ IDZ DO IDZ DO ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG KATALOG KSI¥¯EK KATALOG KSI¥¯EK TWÓJ KOSZYK TWÓJ KOSZYK CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE O NOWOCIACH ZAMÓW INFORMACJE O NOWOCIACH ZAMÓW CENNIK ZAMÓW CENNI K CZYTELNIA CZYTELNIA FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE SPIS TRECI SPIS TRECI DODAJ DO KOSZYKA DODAJ DO KOSZYKA KATALOG ONLINE KATALOG ONLINE C++. Strategie i taktyki. Vademecum profesjonalisty Autor: Robert B. Murray T³umaczenie: Przemys³aw Steæ ISBN: 83-7361-323-4 Tytu³ orygina³u: C++ Strategies and Tactics Format: B5, stron: 240 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³aciwych strategii pomaga unikaæ pu³apek i pracowaæ o wiele skuteczniej. Rob Murray dziel¹c siê swoim dowiadczeniem 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êtnoci, ka¿dy rozdzia³ koñczy siê list¹ poruszonych w nim kluczowych zagadnieñ oraz pytaniami maj¹cymi spowodowaæ przemylenia i dyskusje. Ksi¹¿ka przedstawia miêdzy innymi: • Tworzenie w³aciwych 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. Wczenie 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 konferencjach naukowych i technicznych.

description

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...]

Transcript of C++. Strategie i taktyki. Vademecum profesjonalisty

Page 1: C++. Strategie i taktyki. Vademecum profesjonalisty

Wydawnictwo Helion

ul. Chopina 6

44-100 Gliwice

tel. (32)230-98-63

e-mail: [email protected]

PRZYK£ADOWY ROZDZIA£PRZYK£ADOWY ROZDZIA£

IDZ DOIDZ DO

ZAMÓW DRUKOWANY KATALOGZAMÓW DRUKOWANY KATALOG

KATALOG KSI¥¯EKKATALOG KSI¥¯EK

TWÓJ KOSZYKTWÓJ KOSZYK

CENNIK I INFORMACJECENNIK I INFORMACJE

ZAMÓW INFORMACJEO NOWO�CIACH

ZAMÓW INFORMACJEO NOWO�CIACH

ZAMÓW CENNIKZAMÓW CENNIK

CZYTELNIACZYTELNIA

FRAGMENTY KSI¥¯EK ONLINEFRAGMENTY KSI¥¯EK ONLINE

SPIS TRE�CISPIS TRE�CI

DODAJ DO KOSZYKADODAJ DO KOSZYKA

KATALOG ONLINEKATALOG ONLINE

C++. Strategie i taktyki.

Vademecum profesjonalisty

Autor: Robert B. Murray

T³umaczenie: Przemys³aw Steæ

ISBN: 83-7361-323-4

Tytu³ orygina³u: C++ Strategies and Tactics

Format: B5, stron: 240

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

konferencjach naukowych i technicznych.

Page 2: C++. Strategie i taktyki. Vademecum profesjonalisty

����������������� �

� ��������� ��

���������� �������� ��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

���������� ���� � 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

��������!� "�#���� ��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

��������$� %������������ �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

Page 3: C++. Strategie i taktyki. Vademecum profesjonalisty

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

���������� %������������������������ ��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

��������&� ������������� ����'���������������� �(!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

�������� � )������ ���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

��������*� )��������������� �!�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

���������� +�,����-.� ������/������������� � �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

Page 4: C++. Strategie i taktyki. Vademecum profesjonalisty

Spis treści 7

9.14. Prawo Tiemanna .........................................................................................................204

9.15. W skrócie ....................................................................................................................204

9.16. Pytania ........................................................................................................................205

���������(� ���'��� �(�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

����������� ������������� ������0�����122 ���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

)�������� ���

Page 5: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4.

�������������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.

��� �����������������������������������

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):

������������� ������������� ���� �������������� �������

����������������� ������������ ������������� ���� �������������� �������

Każdy obiekt typu ������� jest również obiektem typu ���� (zauważmy, że relacja

odwrotna nie jest prawdziwa — mogą istnieć obiekty typu ����, które nie są obiektami

typu �������). Każda operacja, którą można zastosować do obiektu typu ���� powinna

również mieć sens przy zastosowaniu do obiektu typu ������� (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.

���������������������� ������

Dziedziczenie nie powinno być stosowane w przypadku, gdy klasa bazowa jest składni-

kiem obiektu opisywanego przez klasę pochodną:

Page 6: C++. Strategie i taktyki. Vademecum profesjonalisty

76 C++. Strategie i taktyki. Vademecum profesjonalisty

��������������� ���������� ����� ���������������

����������������� ����������������� ��!"�#��!�����������������$"��������%����

Obiekt typu ������� nie jest specjalnym rodzajem obiektu typu ��� ��, któremu

przypadkiem doczepiono kadłub — ������� jest raczej obiektem złożonym z innych

obiektów, w tym obiektu typu ��� �� (obiekt typu ������� nie jest obiektem typu

��� ��, on ma ��� ��).

Takie niewłaściwe użycie mechanizmu dziedziczenia pozwala użytkownikom stosować

operacje klasy ��� �� do obiektu typu �������:

����������� �����&��'� �������

W wyniku wywołania funkcji ����������zostanie zwrócona długość obiektu typu ��� �

��, a nie obiektu typu �������. 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:

��������������������������������������������������'��

Oznacza to, że w operacjach na części ��� �� obiektu typu ������� trzeba będzie

jawnie wymieniać składową �� typu ��� ��, lecz dzięki temu związek pomiędzy kla-

sami ������� a ��� �� jest jaśniejszy — jest to związek ma (agregacji), a nie jest (ge-

neralizacji).

����������������������������������������

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 ����

o dwóch rodzajach prędkości — prędkości normalnej, ���������, mierzonej względem

podłoża oraz prędkości lotu, ��������������, mierzonej względem powietrza, która

w obecności wiatru można być różna od wartości ���������:

Page 7: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 77

����������������("���������� ���������� ������������������������� �����������)��� �����������

����������)���!����� ���������������������������������*�!�������������*���%��#������ ����� �����������)��� ���������� ����������������$"��������%����

Ponowna deklaracja składowej ��������� �������������� jako prywatnej stanowi próbę

uniemożliwienia wywołania funkcji ������������ dla obiektu typu ��������� :

�����������+����� ���������)���!����������$"��������%����

����)���!�,����&���!������+��� ��������&���-.�������)��� �����,�/"*���������������������������������������������������"��!���������)��� ���������!�����,�

Próba ta jest jednak nieudana, ponieważ zakazaną funkcję składową można mimo wszystko

wywołać poprzez wskaźnik typu �����:

����,����&���!������+��� ��������&���-.�������)��� �������0�!�����

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 �������������� pojazdu

lądowego ma w ogóle sens. Jeśli uznamy, że nie, to będzie znaczyło, że deklaracja funkcji

składowej �������������� nie powinna występować w żadnej klasie, która jest klasą

bazową dla klasy ��������� . 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ę ������������� , z której będą wyprowadzane

wszystkie pojazdy posiadające prędkość lotu:

������������� ���������� ����������������������

����������)���!����� �������������,�'''�,����

����������)��!���������� ������������ ���������� �����������)��� ��������������������$"��������%����

Page 8: C++. Strategie i taktyki. Vademecum profesjonalisty

78 C++. Strategie i taktyki. Vademecum profesjonalisty

Przy takiej hierarchii klas nie mamy możliwości wywołania funkcji ������������ wobec

obiektu typu ��������� , nawet poprzez wskaźnik typu ����� (propozycję innego

sposobu rozwiązania tego problemu zawiera pytanie 1 na końcu tego rozdziału).

������������������������������������������������

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

��������������������������

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 ���

����� na wskaźnik lub referencję do klasy �����:

���� ���!)������1�����23�1������1������4��������"������������ �4 ���!)�������3�����������

Powyższy kod opiera się na fakcie, że Koło jest Kształtem, a więc referencję do Koła

� można przekazać do każdej funkcji posiadającej parametr typu �����!. 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.

�������������������� ����

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

Page 9: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 79

Powtórka: Dziedziczenie publiczne, chronione i prywatne

W języku C++ istnieją trzy rodzaje dziedziczenia: ��"���, �������� oraz ���#���. We wszystkichformach 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 dziedziczeniaróżnią się elementami, które są widoczne dla użytkownika klasy pochodnej (a nie twórcy klasypochodnej) 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:

������1�������� ���������1���������������

������1������� �����1��������������$"��������%��� ���������1���� ������������ �������������������������

Przy zastosowaniu dziedziczenia publicznego, składowe publiczne klasy bazowej pozostają pu-bliczne w klasie pochodnej, a składowe chronione klasy bazowej pozostają chronione w klasiepochodnej:

1������5������ �!$����"������������ �5�'������������61�-�7 ���������������������"��!*�� �����*��������������� �����������������!��

Wskaźnik do klasy pochodnej może zostać niejawnie przekonwertowany na wskaźnik do publicznejklasy bazowej:

1�����,����&���!�1����5������61�-�1������������ �����*����������������������������������*����!*

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:

������8��� �+��� ���������8��� �+��������+��,�&�99�������+�����������:;�������������������-��������������� ���������������

������ ���)����7�� �����������8��� �+��� ��������� ���)����7�� ��������+��,�����������!����)���!���)��������������������

Składowe klasy $���������%��� mogą korzystać ze składowych publicznych i chronionych klasy

&������:

<���� ��=�����'+.����7 ����������������� ���)����7�� ���!����)���!���)�������������������

Page 10: C++. Strategie i taktyki. Vademecum profesjonalisty

80 C++. Strategie i taktyki. Vademecum profesjonalisty

7����������&�>����=�� ��������??�����������7��@��������,�+���:�;����������������� ���4�������� ���>��

Użytkownicy klasy $���������%��� nie mogą jednak wywoływać żadnych składowych klasy &������:

������������ ���)����7�� ����95554A4A9����������� ��&���'� �����������/"*����������������������������������������������"��!��� �������������!����

Użytkownicy nie mogą również wykonać niejawnej konwersji wskaźnika do klasy pochodnej nawskaźnik do klasy bazowej:

����7�������� ���)����7�� ���95554A4A9������8��� �+,�!���&�2������/"*�������������8��� �+����������������������������������!���*�����*����!*�

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źnikdo 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:

������8��� �+��� ��������'''��������� ���������������

������ ���)����7�� �����������8��� �+��� ��������'''����8��� �+��� ���������0�������������%� ��

Dzięki temu użytkownicy będą mieli możliwość wywoływania dla obiektu typu $���������%���funkcji ������ tak, jak gdyby została ona zadeklarowana w następujący sposób:

������ ���)����7�� �����������8��� �+��� ��������'''��������� ������������ ���8��� �+��� �����������

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 $���������%��� zaprezentowanej w ramce „Powtórka:

Dziedziczenie publiczne, chronione i prywatne” powinna zostać zmodyfikowana w na-

stępujący sposób:

Page 11: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 81

<���� ��=�����'+.����7 ��������������������8��� �+������B�������!��#�����-��������� ���������8��� �+��������+��,�&�99�������+�����������:;�������������������-��������������� ���������������

������ ���)����7�� �������������8��� �+���� ��������� ���)����7�� ��������+��,�����������!����)���!���)��������������������

��� ���)����7�� ���!����)���!���)�����������������������7����������&�>����=��'� ��������??�����������7��@��������:�;����������������� ���4�������� ���>��

Jedyne zmiany w treści kodu funkcji składowych klasy $���������%��� wynikają z ko-

nieczności uściślenia niejawnych odwołań do składowych klasy bazowej &������ nazwą

składowej � typu &������. 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 &������.

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 '"������ . Każde wywołanie funkcji "���������� �����(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:

������(����������� ���������(������������������� ���C(���������������

Page 12: C++. Strategie i taktyki. Vademecum profesjonalisty

82 C++. Strategie i taktyki. Vademecum profesjonalisty

���������)��� ������������(��������,��������!+��������&����)������������)���������������������������

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 '"���

���� jako prywatnej klasy bazowej:

������D��������������(����������� ������������� ���CD�����������������$"��������%����

Przesłaniając destruktor wirtualny zapewniamy, że instrukcja �����(��) występująca

w treści funkcji "���������� ��� w przypadku, gdy będzie dotyczyć obiektu klasy

*���, spowoduje wywołanie destruktora klasy *���. Dzięki zastosowaniu dziedzicze-

nia prywatnego zachowujemy możliwość zmiany implementacji klasy *��� polegającej

na użyciu jakiegoś innego mechanizmu zbierania nieużytków.

�������������������� �� ��

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:

������8��� �+����,�'''�,����

������ ���)����7�� ������������8��� �+����,�'''�,����

������ ���)�������!����� ����� ���)����7�� ����,�'''�,����

Funkcje składowe klasy $������������� mają dostęp do składowych publicznych i chro-

nionych części podchodzącej od klasy &������.

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ą:

������8��� �+����,�'''�,����

������ ���)����7�� ��������������8��� �+���

Page 13: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 83

�����������$"��������%����

������ ���)�������!����� ����� ���)����7�� ����,�'''�,����

Taka przeróbka znacznie upraszcza hierarchię dziedziczenia. Wydajność jest taka sama,

a funkcje składowe klasy $������������� mają wciąż dostęp do części obiektu pocho-

dzącej od klasy &������ (chociaż teraz muszą odwoływać się do składowej �).

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.

�!��� �� "#�������������$����������� ��

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:

������������� ������������� ����������������� ���������� ����������������

�����������+����� ������������ ������������� ����������������� �������

������6����)��!������� ������������ ������������� ����������������� �������

Przy dziedziczeniu istnieje jednak znacznie silniejsze ograniczenie dotyczące składowych

�� ����� klas ������� oraz +���������� 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 ���� powinien „przyspieszać tak, jak robi to ����” — 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 ����, model abstrakcyjny funkcji �� ����� mógłby określać, że

przyspieszenie pojazdu zmienia jego ������� o określoną wartość, tj.:

( ) ( )xpredkoscpredkoscxprzyspieszstaranowa

+==⇒

Gdy tylko ta część abstrakcji zostanie opisana, wszystkie klasy pochodne powinny być

z nią zgodne.

Page 14: C++. Strategie i taktyki. Vademecum profesjonalisty

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:

���������������2����������'���������-�'������������

i kod ten będzie działać w przypadku wszystkich Pojazdów:

6����)��!�����������������������

�����+�������!������������������!������

W przyszłości mogą zostać dodane nowe rodzaje obiektów typu ���� i będą one po-

prawnie działać z kodem, który został zaimplementowany w czasach, kiedy one jeszcze

nie istniały!

����������������� ������������ ������������� ����������������� �������

��������������)EFE���������������)EFE������G�����������%3�H�������#����������@

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 ���� 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 ������� i �� ����� 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.:

������0����������� ����������+���� ������������� ����������������� �������

���0������������������� ������������������0�������������������*�����������@���������+������������A,��������

Autor klasy ,������� źle zrozumiał, jak powinna działać funkcja składowa �� �����.

Dlatego klasa ,������� nie jest zgodna z modelem abstrakcyjnym klasy ����.

Rozważmy, co się stanie, jeśli dla obiektu typu ,������� poruszającego się z prędkością

100 km/h wywołamy funkcję ��� ���. Funkcja ��� ��� wykona następujące wywołanie

�'���������-�'�����������

Page 15: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 85

które w tym przypadku spowoduje wywołanie funkcji

0������������������-4>>��

co z kolei wywoła funkcję

�����+������������-A>>��

Po wywołaniu funkcji ��� ��� nasz ,������� 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 ,������� nie

jest zgodna z modelem abstrakcyjnym klasy ����.

�%�&������������ � ��������

Nasza pierwotna klasa ���� zawiera deklarację funkcji składowej �� �����. Umie-

ściliśmy ją w tej klasie, ponieważ �� ����� 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ę �������� �����? Nie prze-

widujemy w ogóle tworzenia obiektów typu ����. Klasa ���� jest za to klasą bazową,

która opisuje pojęcia wspólne dla zbioru klas pochodnych. W zamierzeniu klasa ����ma być używana wyłącznie jako klasa bazowa, a funkcja �� ����� zostanie przesło-

nięta w każdej klasie pochodnej. Nie spodziewamy się więc, żeby ktoś kiedykolwiek

wywołał funkcję �������� �����. Jedno podejście mogłoby polegać na zdefinio-

waniu wersji, która w przypadku wywołania wyświetli komunikat o błędzie:

������������������� ���������������==�9D�!�"����7 ��������������������IJ�9��������������

lecz takie podejście będzie wykrywać brak przesłonięcia funkcji �� ����� 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 �������� ����� jako tzw. funkcji czysto wirtualnej.

Dzięki zadeklarowaniu klasy ���� jako klasy abstrakcyjnej, kompilator będzie genero-

wać błąd kompilacji przy każdej próbie utworzenia obiektu typu ����. 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 ����, które opisują zbiory klasy pochodnych.

Destruktor nigdy nie powinien być funkcją czysto wirtualną:

������������� ������������� ���C�������&�>�����("�������"�����������$"��������%����

Page 16: C++. Strategie i taktyki. Vademecum profesjonalisty

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

������K���������� �������7���&�>���

jest tzw. funkcją czysto wirtualną. Nie trzeba podawać żadnej definicji funkcji czysto wirtualnej

/��%��. 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 /��% zostanie przesłonięta, to ta klasa będzie jużklasą konkretną (nieabstrakcyjną):

������0��� �����K����������7�����

Abstrakcyjna klasa bazowa służy do deklarowania interfejsu bez deklarowania pełnego zbioruimplementacji dla tego interfejsu. Taki interfejs określa operacje abstrakcyjne realizowane przezwszystkie obiekty wyprowadzone z tej klasy — obowiązek zapewnienia implementacji dla tychoperacji abstrakcyjnych spoczywa już na klasach pochodnych. Na przykład:

������������� ������������� ���� �������������� �����&�>��������� ���� ��������������&�>���

Ponieważ klasa ���� jest abstrakcyjna, próba utworzenia obiektu typu ���� powoduje błądkompilacji:

�����������/"*�����������������������������������������������������

Aby móc użyć klasy ����0(����� dla niej utworzyć klasy pochodne:

�����������+����� ������������ ������������� ���� �������������� ������������� ���� ����������������

������L�!������ ������������ ������������� ���� �������������� ������������� ���� ����������������

Ponieważ w klasach ������� oraz 1���� wszystkie funkcje czysto wirtualne klasy bazowej zostałyprzesłonięte, możemy tworzyć obiekty obydwu klas.

Chociaż nie mogą istnieć żadne obiekty typu ����, to jednak możemy używać wskaźników i refe-rencji do tego typu:

���������������2����������'���������-�'������������

Page 17: C++. Strategie i taktyki. Vademecum profesjonalisty

Rozdział 4. � Dziedziczenie 87

Klasa pochodna, która dziedziczy (nie przesłania) funkcję czysto wirtualną jest także abstrakcyjna:

����������)���!����� �������������

����)���!��������/"*������������������������������������������)���!�������������������

Destruktor ������2���� będą wywoływać destruktory każdej klasy wyprowadzonejz klasy ����. 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.

�'�(�����)*������*������ ����������������������$

Sposób obsługi dziedziczenia przez język C++ zawiera kilka sztuczek. Przyjrzyjmy się im:

�� ����!��"������������#��$������������

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 konstruktorakopiującego, automatycznie zostanie utworzony konstruktor kopiujący, którybędzie wywoływać konstruktory kopiujące niestatycznych danych składowychoraz klas bazowych.

� Destruktor. Jeśli nie zadeklarujemy destruktora, a dowolna z niestatycznych danychskładowych lub klas bazowych posiada destruktor, to automatycznie zostanieutworzony destruktor, który będzie wywoływać destruktory niestatycznych danychskładowych oraz klas bazowych. Destruktor ten będzie wirtualny, jeśli dowolnaz klas bazowych posiada destruktor wirtualny.

� Operator przypisania. Jeśli nie zadeklarujemy operatora przypisania, automatyczniezostanie utworzony operator przypisania, który będzie wywoływać operatoryprzypisania niestatycznych danych składowych oraz klas bazowych.

� Ukryte funkcje składowe. Jeśli funkcja składowa klasy bazowej nie jest przesłoniętaw klasie pochodnej, a w tej klasie pochodnej zadeklarowana jest funkcja o tej samejnazwie, lecz o różnych argumentach, to funkcja występująca w klasie bazowejbędzie ukryta. Na przykład:

�����������+���� ������������������"*����#M�������%�������������� �����������������

������N ���������� ���������N ������������

Page 18: C++. Strategie i taktyki. Vademecum profesjonalisty

88 C++. Strategie i taktyki. Vademecum profesjonalisty

������O�����������)�����+����� ����������+���� ������������������"*����#M�������%�������������� ��N �������2������P���!��7 ����%������+������� ��������

Nasz 3���������� �������� może być kierowany za pomocą autopilota, lecz

jednocześnie ukryliśmy wersję funkcji ������ występującą w klasie bazowej:

O�����������)�����+�����'���� ��F5������/"*�������������������H��� �!���M����������������������� ���� �N ������������ ����

Jeśli nie chcemy, aby funkcja klasy bazowej była ukryta, musimy ją ponownie

zadeklarować w klasie pochodnej:

������O�����������)�����+����� ����������+���� ������������������"*����#M�������%�������������� ����������������+������� ������������������� ��N �������2����

Niektóre kompilatory języka C++, w przypadku gdy funkcja zadeklarowana

w klasie pochodnej powoduje ukrycie funkcji klasy bazowej, generują ostrzeżenie.

�� ����%����������� �����������#��&�����������������������

Przy przesłanianiu funkcji wirtualnej (lub czysto wirtualnej) nie trzeba jawnie określać

słowa kluczowego #������ — 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:

������/��!���� ������������� �������7�����

���������+������� �����/��!���� �������������7��������$!��!�H�������������9���� �������7��9��

Umieszczanie słowa kluczowego #������ 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.

�� ����'������������������� �������������"�������������(�������������(�

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

Page 19: C++. Strategie i taktyki. Vademecum profesjonalisty

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 ��� dla klasy pochodnej.

Na przykład:

������/��!���� ���������/��!������������ ���������� �����)������������������ ��==�9/��!���/��!����J�9�����

���������+������� �����/��!���� ������������+�������������� ���������� �����)������������������ ��==�9���+��������+������J�9�����

/��!���/��!������������ �����)��������

������������/��!�����������+�������

Program ten wypisze na ekranie:

/��!���/��!����/��!���/��!����

Wywołanie funkcji �������������� w treści konstruktora klasy ����� będzie zawsze

powodować wywołanie składowej �����������������������, nawet jeśli konstruktor

ten tworzy część typu ����� obiektu typu �������.

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 ����� obiektu typu �������, nie istnieje jeszcze żadna z danych składowych klasy

�������. Wywołanie wersji funkcji wirtualnej z klasy ������� nie miałoby więc sensu,

ponieważ wersja ta próbowałby prawdopodobnie odwoływać się do niezainicjalizowanych

danych składowych klasy ������� (patrząc na ten problem z innej strony, można powie-

dzieć, że gdy wywoływany jest konstruktor klasy �����, obiekt nie jest jeszcze właściwie

obiektem typu �������, więc składowe klasy ������� nie powinny być wywoływane).

Ta sama logika ma zastosowanie wobec wywołań funkcji wirtualnych w treści destruktorów:

/��!���C/��!������������ �����)��������

Ten destruktor będzie zawsze wywoływał funkcję ���������������������, nawet jeśli

niszczymy część typu ����� obiektu typu �������. W chwili wywołania destruktora klasy

����� dane składowe klasy ������� są już zniszczone, więc wywołanie wersji funkcji

���������������� z klasy ������� nie miałoby sensu.

Page 20: C++. Strategie i taktyki. Vademecum profesjonalisty

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:

/��!���/��!������������ �����)��������������D�!�" ���7 ����%�/��!������ �����)����������/��!��,���&���!����+����������-.��� �����)����������D�!�" ���7 ����%����+�������� �����)�������

�+�,����)���

� 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.

�-�.������

�� Załóżmy, że w naszej hierarchii klas ���� chcielibyśmy zdefiniować ��������

���� pojazdu lądowego jako synonim jego składowej �������. W jaki sposób

wpłynie to na hierarchię klas ����? Czy zmiana ta uprości, czy raczej utrudni

korzystanie z tych klas?

�� W jaki sposób zapewnisz, aby klasa ���� potwierdzała, że każda implementacja

funkcji �� ����� w klasie pochodnej jest zgodna z modelem abstrakcyjnym?

(Wskazówka: możesz sprawić, aby klasy pochodne zamiast funkcji �� �����

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�� :

�����+�����'���������������A>��

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?