1233


Rozdział 12.
Dziedziczenie

W poprzednim rozdziale poznałeś wiele zagadnień[Author ID1: at Wed Oct 31 11:19:00 2001 ]relacji[Author ID1: at Wed Oct 31 11:19:00 2001 ] związanych z projektowaniem obiektowym, włącznie [Author ID2: at Wed Nov 21 09:18:00 2001 ]m.in. [Author ID2: at Wed Nov 21 09:18:00 2001 ]z[Author ID2: at Wed Nov 21 09:18:00 2001 ]e[Author ID1: at Wed Oct 31 11:19:00 2001 ] [Author ID2: at Wed Nov 21 09:18:00 2001 ]relacj[Author ID1: at Wed Oct 31 11:19:00 2001 ]ą[Author ID1: at Wed Oct 31 11:19:00 2001 ][Author ID2: at Wed Nov 21 09:18:00 2001 ]ę[Author ID2: at Wed Nov 21 09:18:00 2001 ] [Author ID1: at Wed Oct 31 11:19:00 2001 ]specjalizacji/[Author ID1: at Wed Oct 31 11:19:00 2001 ]ą i [Author ID1: at Wed Oct 31 11:20:00 2001 ]generalizacji[Author ID1: at Wed Oct 31 11:20:00 2001 ]ą[Author ID1: at Wed Oct 31 11:20:00 2001 ]. Język C++ implementuje ją[Author ID1: at Wed Oct 31 11:20:00 2001 ]e[Author ID1: at Wed Oct 31 11:20:00 2001 ] poprzez dziedziczenie.

W [Author ID2: at Wed Nov 21 09:18:00 2001 ]Z [Author ID2: at Wed Nov 21 09:18:00 2001 ]tym[Author ID2: at Wed Nov 21 09:18:00 2001 ]ego[Author ID2: at Wed Nov 21 09:18:00 2001 ] rozdziale[Author ID2: at Wed Nov 21 09:18:00 2001 ]łu[Author ID2: at Wed Nov 21 09:18:00 2001 ] dowiesz się:

Czym jest dziedziczenie?

Czym jest pies? Co widzisz, gdy patrzysz na swoje zwierzę? Ja widzę cztery łapy na służbie[Author ID2: at Wed Nov 21 09:19:00 2001 ]i[Author ID2: at Wed Nov 21 09:19:00 2001 ] pyska[Author ID2: at Wed Nov 21 09:19:00 2001 ]. Biolodzy widzą sieć interesujących organów, fizycy — atomy i działające [Author ID2: at Wed Nov 21 09:19:00 2001 ]różnorodne [Author ID2: at Wed Nov 21 09:19:00 2001 ]siły, zaś taksonom widzi przedstawiciela gatunku canine domesticus.

Właśnie ten ostatni przypadek interesuje nas w tym momen[Author ID2: at Wed Nov 21 09:19:00 2001 ]cie[Author ID2: at Wed Nov 21 09:19:00 2001 ]Skupmy się na tym ostatnim przypadku[Author ID2: at Wed Nov 21 09:19:00 2001 ]. Pies jest przedstawicielem psowatych, psowate są przedstawicielami ssaków, i tak dalej. Taksonomowie dzielą świat żywych stworzeń na królestwa, typy, klasy, rzędy, rodziny, rodzaje i gatunki.

Hierarchia specjalizacji/[Author ID1: at Wed Oct 31 11:20:00 2001 ]-[Author ID1: at Wed Oct 31 11:20:00 2001 ]generalizacji ustanawia relację typu jest-czymś. Homo sapiens jest przedstawicielem naczelnych. Taką relację widzimy wszędzie wokół[Author ID2: at Wed Nov 21 09:20:00 2001 ]: wóz kempingowy jest rodzajem samochodu, który z kolei jest rodzajem pojazdu. Budyń jest rodzajem deseru, który jest rodzajem pożywienia.

Gdy mówimy,[Author ID2: at Wed Nov 21 09:20:00 2001 ] że coś jest rodzajem czegoś innego, zakładamym[Author ID2: at Wed Nov 21 09:20:00 2001 ] że stanowi to specjalizację tej rzeczy. Tak więc s[Author ID2: at Wed Nov 21 09:20:00 2001 ]S[Author ID2: at Wed Nov 21 09:20:00 2001 ]amochód jest zatem[Author ID2: at Wed Nov 21 09:20:00 2001 ]specjalnym rodzajem pojazdu.

Dziedziczenie i wyprowadzanie

Pies dziedziczy — to jest,[Author ID2: at Wed Nov 21 09:21:00 2001 ] automatycznie otrzymuje — wszystkie cechy ssaka. Ponieważ jest ssakiem, wiemy że [Author ID2: at Wed Nov 21 09:21:00 2001 ]porusza się oraz [Author ID2: at Wed Nov 21 09:21:00 2001 ]i [Author ID2: at Wed Nov 21 09:21:00 2001 ]oddycha powietrzem. Wszystkie ssaki, z definicji, poruszają się i oddychają. Pies wzbogaca te elementy o cechy takie jak,[Author ID2: at Wed Nov 21 09:21:00 2001 ] szczekanie, machanie ogonem, zjadanie dopiero co ukończonego rozdziału mojej książki, warczenie,[Author ID2: at Wed Nov 21 09:21:00 2001 ] gdy próbuję zasnąć... Przepraszam. Gdzie skończyłem? A, wiem:

Psy możemy podzielić na psy przeznaczone [Author ID2: at Wed Nov 21 09:22:00 2001 ]do pracy, psy do sportów i teriery, zaś psy do sportów możemy podzielić na psy myśliwskie, spaniele i tak dalej. Można także dokonywać dalszego podziału, na przykład psy myśliwskie można podzielić na L[Author ID2: at Wed Nov 21 09:22:00 2001 ]l[Author ID2: at Wed Nov 21 09:22:00 2001 ]abradory czy G[Author ID2: at Wed Nov 21 09:22:00 2001 ]g[Author ID2: at Wed Nov 21 09:22:00 2001 ]oldeny.

Golden jest rodzajem psa myśliwskiego, będącego [Author ID2: at Wed Nov 21 09:22:00 2001 ]który jest [Author ID2: at Wed Nov 21 09:22:00 2001 ]psem do sportów, należącym[Author ID2: at Wed Nov 21 09:22:00 2001 ]ego[Author ID2: at Wed Nov 21 09:22:00 2001 ] do rodzaju psów, czyli będącego ssakiem, a więc zwierzęciem, czyli rzeczą żywą. Tę hierarchię przedstawia rysunek 12.1.

Rys. 12.1. Hierarchia zwierząt

0x01 graphic

C++ próbuje reprezentować te relacje,[Author ID2: at Wed Nov 21 09:22:00 2001 ] umożliwiając nam definiowanie klas, które są wyprowadzane z innych klas. Wyprowadzanie jest sposobem wyrażenia relacji typu jest-czymś. Nową klasę, Dog (pies),[Author ID2: at Wed Nov 21 09:22:00 2001 ] można wyprowadzić z klasy Mammal (ssak). Nie musimy wtedy wyraźnie mówić,[Author ID2: at Wed Nov 21 09:22:00 2001 ] że pies porusza się, gdyż dziedziczy tę cechę od ssaków.

Klasa dodająca nowe właściwości do istniejącej już klasy jest wyprowadzona z klasy oryginalnej[Author ID2: at Wed Nov 21 09:23:00 2001 ]pierwotnej[Author ID2: at Wed Nov 21 09:23:00 2001 ]. Ta oryginalna [Author ID2: at Wed Nov 21 09:23:00 2001 ]pierwotna [Author ID2: at Wed Nov 21 09:23:00 2001 ]klasa jest nazywana klasą bazową.

Jeśli klasa Dog jest wyprowadzona z klasy Mammal, oznacza to,[Author ID2: at Wed Nov 21 09:23:00 2001 ] że klasa Mammal jest klasą bazową (nadrzędną) klasy Dog. Klasy wyprowadzone (pochodne) stanową nadzbiór swoich klas bazowych. Tak jak p[Author ID2: at Wed Nov 21 09:23:00 2001 ]P[Author ID2: at Wed Nov 21 09:23:00 2001 ]ies przejmuje swoje cechy od ssaków, tak klasa Dog przejmie pewne metody lub dane klasy Mammal.

Zwykle klasa bazowa posiada więcej niż jedną klasę pochodną. Ponieważ zarówno psy, jak i koty oraz konie są ssakami, więc ich klasy [Author ID2: at Wed Nov 21 09:23:00 2001 ]zostały [Author ID2: at Wed Nov 21 09:23:00 2001 ]były wyprowadzone z klasy Mammal.

Królestwo zwierząt

Aby ułatwić przedstawienie procesu [Author ID2: at Wed Nov 21 09:23:00 2001 ]dziedziczenia i wyprowadzania, w tym rozdziale skupimy się na związkach pomiędzy różnymi klasami reprezentującymi zwierzęta. Możesz sobie wyobrazić,[Author ID2: at Wed Nov 21 09:24:00 2001 ] że bawimy się w dziecięcą grę — symulację farmy.

Z czasem o[Author ID2: at Wed Nov 21 09:24:00 2001 ]O[Author ID2: at Wed Nov 21 09:24:00 2001 ]pracujemy cały zestaw zwierząt, obejmujący konie, krowy, psy, koty, owce,[Author ID2: at Wed Nov 21 09:24:00 2001 ] itd. Stworzymy metody dla tych klas, tak [Author ID2: at Wed Nov 21 09:24:00 2001 ]aby zwierzęta mogły funkcjonować tak,[Author ID2: at Wed Nov 21 09:24:00 2001 ] jak oczekiwałoby tego dziecko, ale na razie każdą z tych metod zastąpimy zwykłą instrukcją wydruku.

Zastępowanie[Author ID1: at Wed Oct 31 11:23:00 2001 ]Zminimalizowanie[Author ID1: at Wed Oct 31 11:23:00 2001 ][Author ID2: at Wed Nov 21 09:26:00 2001 ]Minimalizowanie[Author ID2: at Wed Nov 21 09:26:00 2001 ] funkcji ([Author ID1: at Wed Oct 31 11:24:00 2001 ]czyli [Author ID1: at Wed Oct 31 11:25:00 2001 ]pozostawienie tylko jej [Author ID1: at Wed Oct 31 11:24:00 2001 ]pnia[Author ID1: at Wed Oct 31 11:24:00 2001 ][Author ID2: at Wed Nov 21 09:26:00 2001 ]szkieletu[Author ID2: at Wed Nov 21 09:26:00 2001 ])[Author ID1: at Wed Oct 31 11:25:00 2001 ] [Author ID1: at Wed Oct 31 11:24:00 2001 ]oznacza, [Author ID2: at Wed Nov 21 09:26:00 2001 ] [Author ID2: at Wed Nov 21 09:26:00 2001 ]że napiszemy tylko tyle kodu, ile wystarczy do [Author ID2: at Wed Nov 21 09:26:00 2001 ]aby [Author ID2: at Wed Nov 21 09:26:00 2001 ]pokazać[Author ID2: at Wed Nov 21 09:27:00 2001 ]nia,[Author ID2: at Wed Nov 21 09:27:00 2001 ] że funkcja została wywołana.[Author ID2: at Wed Nov 21 09:27:00 2001 ],[Author ID2: at Wed Nov 21 09:27:00 2001 ] pozostawiając [Author ID2: at Wed Nov 21 09:27:00 2001 ]s[Author ID2: at Wed Nov 21 09:27:00 2001 ]S[Author ID2: at Wed Nov 21 09:27:00 2001 ]zczegóły pozostawimy [Author ID2: at Wed Nov 21 09:27:00 2001 ]na później, gdy będziemy mieć więcej czasu. Jeśli tylko masz ochotę, możesz wzbogacić minimalny kod zaprezentowany w tym rozdziale i[Author ID2: at Wed Nov 21 09:27:00 2001 ],[Author ID2: at Wed Nov 21 09:27:00 2001 ] sprawiając[Author ID2: at Wed Nov 21 09:27:00 2001 ]ć,[Author ID2: at Wed Nov 21 09:27:00 2001 ] by zwierzęta zachowywały się bardziej realistycznie.

Składnia wyprowadzania

Gdy deklarujesz klasę, możesz wskazać klasę, od której pochodzi, zapisując po nazwie tworzonej klasy dwukropek, rodzaj wyprowadzania (publiczny lub inny) oraz klasę bazową. Oto przykład:

class Dog : public Mammal

Rodzaj wyprowadzania omówimy [Author ID2: at Wed Nov 21 09:27:00 2001 ]opiszemy [Author ID2: at Wed Nov 21 09:27:00 2001 ]w dalszej części rozdziału. Na razie zawsze [Author ID2: at Wed Nov 21 09:27:00 2001 ]będziemy używać wyprowadzania publicznego (oznaczonego słowem kluczowym public). Klasa bazowa musi być wcześniej [Author ID2: at Wed Nov 21 09:27:00 2001 ]zdefiniowana wcześniej[Author ID2: at Wed Nov 21 09:27:00 2001 ], gdyż w przeciwnym razie kompilator zgłosi błąd. Listing 12.1 ilustruje sposób deklarowania klasy Dog [Author ID2: at Wed Nov 21 09:28:00 2001 ], [Author ID2: at Wed Nov 21 09:28:00 2001 ]wyprowadzonej z klasy Mammal.

Listing 12.1. Proste dziedziczenie

0: //Listing 12.1 Proste dziedziczenie

1:

2: #include <iostream>

3: using namespace std;

4:

5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

6:

7: class Mammal

8: {

9: public:

10: // konstruktory

11: Mammal();

12: ~Mammal();

13:

14: //akcesory

15: int GetAge() const;

16: void SetAge(int);

17: int GetWeight() const;

18: void SetWeight();

19:

20: //inne metody

21: void Speak() const;

22: void Sleep() const;

23:

24:

25: protected:

26: int itsAge;

27: int itsWeight;

28: };

29:

30: class Dog : public Mammal

31: {

32: public:

33:

34: // konstruktory

35: Dog();

36: ~Dog();

37:

38: // akcesory

39: BREED GetBreed() const;

40: void SetBreed(BREED);

41:

42: // inne metody

43: WagTail();

44: BegForFood();

45:

46: protected:

47: BREED itsBreed;

48: };

Ten kod nie wyświetla wyników, gdyż zawiera jedynie zestaw deklaracji klas ([Author ID2: at Wed Nov 21 09:28:00 2001 ]bez ich implementacji)[Author ID2: at Wed Nov 21 09:28:00 2001 ]. Mimo to jest [Author ID2: at Wed Nov 21 09:28:00 2001 ]można [Author ID2: at Wed Nov 21 09:28:00 2001 ]w nim wiele do [Author ID2: at Wed Nov 21 09:28:00 2001 ]zobaczenia[Author ID2: at Wed Nov 21 09:28:00 2001 ][Author ID2: at Wed Nov 21 09:28:00 2001 ].

Analiza:[Author ID2: at Wed Nov 21 09:28:00 2001 ]

W liniach od 7.[Author ID2: at Wed Nov 21 09:28:00 2001 ] do 28.[Author ID2: at Wed Nov 21 09:28:00 2001 ] deklarowana jest klasa Mammal (ssak). Zwróć uwagę,[Author ID2: at Wed Nov 21 09:28:00 2001 ] że w tym przykładzie klasa Mammal nie jest wyprowadzana z żadnej innej klasy. W rzeczywistym [Author ID2: at Wed Nov 21 09:28:00 2001 ]realnym [Author ID2: at Wed Nov 21 09:28:00 2001 ]świecie ssaki pochodzą od (to znaczy,[Author ID2: at Wed Nov 21 09:29:00 2001 ] że są rodzajem) zwierząt. W programie C++ możesz zaprezentować jedynie część informacji, które posiadasz na temat danego obiektu. Rzeczywistość jest zdecydowanie zbyt skomplikowana,[Author ID2: at Wed Nov 21 09:29:00 2001 ] aby uchwycić ją w całości, więc każda hierarchia w C++ jest umowną reprezentacją dostępnych danych. Sztuka dobrego projektowania polega na reprezentowaniu interesujących nas obszarów tak,[Author ID2: at Wed Nov 21 09:29:00 2001 ] aby reprezentowały rzeczywistość [Author ID2: at Wed Nov 21 09:29:00 2001 ]w wystarczająco dobry[Author ID2: at Wed Nov 21 09:29:00 2001 ]możliwie najlepszy[Author ID2: at Wed Nov 21 09:29:00 2001 ] sposób reprezentowały rzeczywistość[Author ID2: at Wed Nov 21 09:29:00 2001 ].

Hierarchia musi się gdzieś zaczynać; w tym programie rozpoczyna się od klasy Mammal. Z powodu tej decyzji[Author ID2: at Wed Nov 21 09:29:00 2001 ]go[Author ID2: at Wed Nov 21 09:29:00 2001 ], pewne zmienne składowe, które mogły należeć do wyższej klasy, nie są tu reprezentowane. Wszystkie zwierzęta z pewnością posiadają na przykład wagę i wiek, więc jeśli klasa Mammal byłaby wyprowadzona z klasy Animal (zwierzę), moglibyśmy oczekiwać,[Author ID2: at Wed Nov 21 09:29:00 2001 ] że dziedziczy te atrybuty. Jednak w naszym przykładzie atrybuty [Author ID2: at Wed Nov 21 09:30:00 2001 ]te atrybuty [Author ID2: at Wed Nov 21 09:30:00 2001 ]występują w klasie Mammal.

Aby utrzymać [Author ID2: at Wed Nov 21 09:30:00 2001 ]zachować [Author ID2: at Wed Nov 21 09:30:00 2001 ]niewielką i spójną postać programu, w klasie Mammal zostało umieszczonych jedynie sześć metod — cztery akcesory oraz metody Speak() (mówienie) i Sleep() (spanie).

Klasa Dog (pies) dziedziczy po klasie Mammal, co wskazuje linia 30. Każdy obiekt typu Dog będzie posiadał trzy zmienne składowe: itsAge (wiek), itsWeight (waga) oraz itsBread (rasa). Zwróć uwagę,[Author ID2: at Wed Nov 21 09:30:00 2001 ] że deklaracja klasy Dog nie obejmuje zmiennych składowych itsAge oraz itsWeight. Obiekty klasy Dog dziedziczą te zmienne od klasy Mammal, razem z metodami tej klasy ([Author ID2: at Wed Nov 21 09:30:00 2001 ]z wyjątkiem operatora kopiującego[Author ID1: at Wed Oct 31 11:28:00 2001 ]i[Author ID1: at Wed Oct 31 11:28:00 2001 ] oraz destruktora i konstruktora)[Author ID2: at Wed Nov 21 09:30:00 2001 ].

Prywatne kontra chronione

Być może w liniach 25.[Author ID2: at Wed Nov 21 09:30:00 2001 ] i 46.[Author ID2: at Wed Nov 21 09:30:00 2001 ] listingu 12.1 zauważyłeś nowe słowo kluczowe dostępu, protected (chronione). Wcześniej dane klasy były deklarowane jako prywatne. Jednak prywatne składowe nie są dostępne dla klas pochodnych. Moglibyśmy uczynić zmienne itsAge i itsWeight składowymi publicznymi, ale nie byłoby to pożądane. Nie chcemy,[Author ID2: at Wed Nov 21 09:30:00 2001 ] by inne klasy mogły bezpośrednio odwoływać się do tych danych składowych.

UWAGA Istnieje argument [Author ID2: at Wed Nov 21 09:30:00 2001 ]powód, [Author ID2: at Wed Nov 21 09:30:00 2001 ]by wszystkie dane składowe klasy oznaczać jako prywatne,[Author ID2: at Wed Nov 21 09:31:00 2001 ] i[Author ID1: at Wed Oct 31 11:28:00 2001 ]a[Author ID1: at Wed Oct 31 11:28:00 2001 ] nigdy[Author ID2: at Wed Nov 21 09:31:00 2001 ]e[Author ID2: at Wed Nov 21 09:31:00 2001 ] jako chronione. Argument [Author ID2: at Wed Nov 21 09:31:00 2001 ]Powód [Author ID2: at Wed Nov 21 09:31:00 2001 ]ten przedstawił Stroustrup (twórca języka C++) w swojej książce The Design and Evolution of C++, ISBN 0-201-543330-3, Addison Wesley, 1994. Metody [Author ID2: at Wed Nov 21 09:31:00 2001 ]C[Author ID2: at Wed Nov 21 09:31:00 2001 ]c[Author ID2: at Wed Nov 21 09:31:00 2001 ]hronione metody [Author ID2: at Wed Nov 21 09:31:00 2001 ]nie są jednak uważane za problematyczne [Author ID2: at Wed Nov 21 09:31:00 2001 ]kłopotliwe [Author ID2: at Wed Nov 21 09:31:00 2001 ]i mogą być bardzo użyteczne.

To, czego p[Author ID2: at Wed Nov 21 09:32:00 2001 ]P[Author ID2: at Wed Nov 21 09:32:00 2001 ]otrzebujemy,[Author ID2: at Wed Nov 21 09:32:00 2001 ] teraz[Author ID2: at Wed Nov 21 09:32:00 2001 ] to[Author ID2: at Wed Nov 21 09:32:00 2001 ] oznaczenie[Author ID2: at Wed Nov 21 09:32:00 2001 ]a[Author ID2: at Wed Nov 21 09:32:00 2001 ], które mówi: „Uczyń te zmienne widocznymi dla tej klasy i dla klasy[Author ID2: at Wed Nov 21 09:32:00 2001 ] z niej wyprowadzonych”. Takim oznaczeniem jest właśnie słowo kluczowe protected — chroniony. Chronione funkcje i dane składowe są w pełni widoczne dla klas pochodnych i nie są dostępne dla innych klas.

Ogólnie, i[Author ID2: at Wed Nov 21 09:32:00 2001 ]I[Author ID2: at Wed Nov 21 09:32:00 2001 ]stnieją trzy specyfikatory dostępu: publiczny (public), chroniony (protected) oraz private (prywatny). Jeśli funkcja posiada obiekt twojej klasy, może odwoływać się do wszystkich jej publicznych funkcji i danych składowych. Z kolei funkcje składowe klasy mogą odwoływać się do wszystkich prywatnych funkcji i danych składowych swojej własnej klasy, oraz do wszystkich chronionych funkcji i danych składowych wszystkich klas, z których ich klasa jest wyprowadzona.

Tak więc, funkcja Dog::WagTail() (macha ogonem) może odwoływać się do prywatnej danej itsBrea[Author ID1: at Wed Oct 31 11:29:00 2001 ]e[Author ID1: at Wed Oct 31 11:29:00 2001 ]d oraz do prywatnych danych klasy Mammal.

Nawet jeśli [Author ID2: at Wed Nov 21 09:32:00 2001 ]gdyby [Author ID2: at Wed Nov 21 09:32:00 2001 ]pomiędzy klasą Mammal [Author ID2: at Wed Nov 21 09:32:00 2001 ], [Author ID2: at Wed Nov 21 09:32:00 2001 ]a klasą Dog występowałyby[Author ID2: at Wed Nov 21 09:32:00 2001 ] inne klasy (na przykład DomesticAnimals — zwierzęta domowe), klasa Dog w dalszym ciągu mogłaby się odwoływać do prywatnych składowych klasy Mammal, zakładając że wszystkie te [Author ID2: at Wed Nov 21 09:32:00 2001 ]inne klasy używałyby dziedziczenia publicznego. Dziedziczenie prywatne zostanie omówione w rozdziale 16., „Dziedziczenie [Author ID2: at Wed Nov 21 09:32:00 2001 ]Z[Author ID2: at Wed Nov 21 09:32:00 2001 ]z[Author ID2: at Wed Nov 21 09:32:00 2001 ]aawansowane dziedziczenie[Author ID2: at Wed Nov 21 09:32:00 2001 ]”.

Listing 12.2 przedstawia sposób tworzenia obiektów typu Dog oraz dostępu do do [Author ID1: at Wed Oct 31 11:29:00 2001 ]danych i funkcji zawartych w tym typie.

Listing 12.2. Użycie klasy [Author ID2: at Wed Nov 21 09:33:00 2001 ]wyprowadzonej klasy[Author ID2: at Wed Nov 21 09:33:00 2001 ]

0: //Listing 12.2 Użycie klasy [Author ID2: at Wed Nov 21 09:33:00 2001 ]wyprowadzonej klasy[Author ID2: at Wed Nov 21 09:33:00 2001 ]

[Author ID2: at Wed Nov 21 09:33:00 2001 ]

1:

2: #include <iostream>

3: using std::cout;

4:

5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

6:

7: class Mammal

8: {

9: public:

10: // konstruktory

11: Mammal():itsAge(2), itsWeight(5){}

12: ~Mammal(){}

13:

14: //akcesory

15: int GetAge() const { return itsAge; }

16: void SetAge(int age) { itsAge = age; }

17: int GetWeight() const { return itsWeight; }

18: void SetWeight(int weight) { itsWeight = weight; }

19:

20: //inne metody

21: void Speak()const { cout << "Dzwiek ssaka!\n"; }

22: void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; }

23:

24:

25: protected:

26: int itsAge;

27: int itsWeight;

28: };

29:

30: class Dog : public Mammal

31: {

32: public:

33:

34: // konstruktory

35: Dog():itsBreed(GOLDEN){}

36: ~Dog(){}

37:

38: // akcesory

39: BREED GetBreed() const { return itsBreed; }

40: void SetBreed(BREED breed) { itsBreed = breed; }

41:

42: // inne metody

43: void WagTail() const { cout << "Macham ogonem...\n"; }

44: void BegForFood() const {cout << "Prosze o jedzenie...\n"; }

45:

46: private:

47: BREED itsBreed;

48: };

49:

50: int main()

51: {

52: Dog fido;

53: fido.Speak();

54: fido.WagTail();

55: cout << "Fido ma " << fido.GetAge() << " lat(a)\n";

56: return 0;

57: }

Wynik:[Author ID2: at Wed Nov 21 09:33:00 2001 ]

Dzwiek ssaka!

Macham ogonem...

Fido ma 2 lat(a)

Analiza:[Author ID2: at Wed Nov 21 09:33:00 2001 ]

W liniach od 7.[Author ID2: at Wed Nov 21 09:33:00 2001 ] do 28.[Author ID2: at Wed Nov 21 09:33:00 2001 ] jest deklarowana klasa Mammal (wszystkie jej funkcje są funkcjami [Author ID2: at Wed Nov 21 09:34:00 2001 ][Author ID2: at Wed Nov 21 09:33:00 2001 ] typ[Author ID2: at Wed Nov 21 09:34:00 2001 ]u[Author ID2: at Wed Nov 21 09:33:00 2001 ] [Author ID2: at Wed Nov 21 09:34:00 2001 ]inline w [Author ID2: at Wed Nov 21 09:34:00 2001 ]— w [Author ID2: at Wed Nov 21 09:34:00 2001 ]celu zaoszczędzenia miejsca na wydruku). W liniach od 30.[Author ID2: at Wed Nov 21 09:34:00 2001 ] do 48.[Author ID2: at Wed Nov 21 09:34:00 2001 ] deklarowana jest klasa Dog, będąca klasą pochodną klasy Mammal. Tak więc[Author ID2: at Wed Nov 21 09:34:00 2001 ]Zatem[Author ID2: at Wed Nov 21 09:34:00 2001 ], z definicji, wszystkie obiekty typu Dog posiadają wiek, wagę i rasę.

W linii 52.[Author ID2: at Wed Nov 21 09:34:00 2001 ] deklarowany jest obiekt typu Dog o nazwie fido. Obiekt fido dziedziczy wszystkie atrybuty klasy Mammal oraz posiada własne atrybuty klasy Dog. Tak więc fido potrafi machać ogonem (metoda WagTail()), ale potrafi także wydawać dźwięki (Speak()) oraz spać (Sleep()).

Konstruktory i destruktory

Obiekty klasy Dog są obiektami klasy Mammal. Taka jest esencja[Author ID2: at Wed Nov 21 09:35:00 2001 ]Na tym właśnie polega[Author ID2: at Wed Nov 21 09:35:00 2001 ] relacji[Author ID2: at Wed Nov 21 09:35:00 2001 ]a[Author ID2: at Wed Nov 21 09:35:00 2001 ] jest-czymś. Gdy tworzony jest obiekt fido, najpierw wywoływany jest jego konstruktor bazowy, tworzący część [Author ID1: at Wed Oct 31 11:30:00 2001 ]klasę[Author ID1: at Wed Oct 31 11:30:00 2001 ] Mammal.[Author ID1: at Wed Oct 31 11:30:00 2001 ]-ową [Author ID1: at Wed Oct 31 11:31:00 2001 ]nowego obiektu.[Author ID1: at Wed Oct 31 11:30:00 2001 ] Następnie wywoływany jest konstruktor klasy Dog, uzupełniający tworzenie obiektu klasy Dog. Ponieważ nie podaliśmy obiektowi żadnych parametrów, w tym przypadku jest wywoływany konstruktor domyślny. Obiekt fido nie istnieje do chwili całkowitego zakończenia tworzenia go, co oznacza,[Author ID2: at Wed Nov 21 09:35:00 2001 ] że musi zostać skonstruowana zarówno jego część Mammal, jak i część Dog.[Author ID2: at Wed Nov 21 09:35:00 2001 ],[Author ID2: at Wed Nov 21 09:35:00 2001 ] To znaczy[Author ID2: at Wed Nov 21 09:35:00 2001 ]czyli [Author ID2: at Wed Nov 21 09:35:00 2001 ], [Author ID2: at Wed Nov 21 09:35:00 2001 ]muszą być wywołane oba konstruktory.

Gdy obiekt fido jest niszczony, najpierw wywoływany jest destruktor klasy Dog, a dopiero potem destruktor klasy Mammal. Każdy destruktor ma okazję uporządkować własną część obiektu. Pamiętaj,[Author ID2: at Wed Nov 21 09:35:00 2001 ] aby posprzątać po swoim psie! Demonstruje to listing 12.3.

Listing 12.3. Wywoływane konstruktory i destruktory

0: //Listing 12.3 Wywoływane konstruktory i destruktory.[Author ID2: at Wed Nov 21 09:35:00 2001 ]

1:

2: #include <iostream>

3: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

4:

5: class Mammal

6: {

7: public:

8: // konstruktory

9: Mammal();

10: ~Mammal();

11:

12: // akcesory

13: int GetAge() const { return itsAge; }

14: void SetAge(int age) { itsAge = age; }

15: int GetWeight() const { return itsWeight; }

16: void SetWeight(int weight) { itsWeight = weight; }

17:

18: // inne metody

19: void Speak()const { std::cout << "Dzwiek ssaka!\n"; }

20: void Sleep()const { std::cout << "Ciiiicho. Wlasnie spie.\n"; }

21:

22:

23: protected:

24: int itsAge;

25: int itsWeight;

26: };

27:

28: class Dog : public Mammal

29: {

30: public:

31:

32: // konstruktory

33: Dog();

34: ~Dog();

35:

36: // akcesory

37: BREED GetBreed() const { return itsBreed; }

38: void SetBreed(BREED breed) { itsBreed = breed; }

39:

40: // inne metody

41: void WagTail() const { std::cout << "Macham ogonkiem...\n"; }

42: void BegForFood() const { std::cout << "Prosze o jedzenie...\n"; }

43:

44: private:

45: BREED itsBreed;

46: };

47:

48: Mammal::Mammal():

49: itsAge(1),

50: itsWeight(5)

51: {

52: std::cout << "Konstruktor klasy Mammal...\n";

53: }

54:

55: Mammal::~Mammal()

56: {

57: std::cout << "Destruktor klasy Mammal...\n";

58: }

59:

60: Dog::Dog():

61: itsBreed(GOLDEN)

62: {

63: std::cout << "Konstruktor klasy Dog...\n";

64: }

65:

66: Dog::~Dog()

67: {

68: std::cout << "Destruktor klasy Dog...\n";

69: }

70: int main()

71: {

72: Dog fido;

73: fido.Speak();

74: fido.WagTail();

75: std::cout << "Fido ma " << fido.GetAge() << " lat(a)\n";

76: return 0;

77: }

Wynik:[Author ID2: at Wed Nov 21 09:35:00 2001 ]

Konstruktor klasy Mammal...

Konstruktor klasy Dog...

Dzwiek ssaka!

Macham ogonkiem...

Fido ma 1 lat(a)

Destruktor klasy Dog...

Destruktor klasy Mammal...

Analiza:[Author ID2: at Wed Nov 21 09:35:00 2001 ]

Listing 12.3 jest podobny do listingu 12.2, z tym [Author ID2: at Wed Nov 21 09:36:00 2001 ]t[Author ID2: at Wed Nov 21 09:36:00 2001 ]ą różnicą, [Author ID2: at Wed Nov 21 09:36:00 2001 ]że konstruktory i destruktory wypisują teraz komunikaty na ekranie. Wywoływany jest najpierw konstruktor klasy Mammal, następnie konstruktor klasy Dog. W tym momencie obiekt klasy Dog już w pełni [Author ID2: at Wed Nov 21 09:37:00 2001 ]istnieje i można wywoływać jego metody. Gdy fido wychodzi z zakresu, wywoływany jest jego destruktor klasy Dog, a następnie destruktor klasy Mammal.

Przekazywanie argumentów do konstruktorów bazowych

Istnieje możliwość[Author ID2: at Wed Nov 21 09:37:00 2001 ]Może się zdarzyć,[Author ID2: at Wed Nov 21 09:37:00 2001 ] że zechcemy przeciążyć konstruktor klasy Mammal tak,[Author ID2: at Wed Nov 21 09:37:00 2001 ] aby przyjmował określony wiek, oraz że zechcemy przeciążyć konstruktor klasy Dog tak,[Author ID2: at Wed Nov 21 09:37:00 2001 ] aby przyjmował rasę. W jaki sposób możemy przesłać właściwe parametry wieku i wagi do odpowiedniego konstruktora klasy Mammal? Co zrobić,[Author ID2: at Wed Nov 21 09:37:00 2001 ] gdy obiekty klasy Dog chcą inicjalizować swoją wagę, a obiekty klasy Mammal nie?

Inicjalizacja klasy bazowej może być wykonana podczas inicjalizacji klasy pochodnej, przez zapisanie nazwy klasy bazowej, po której następują parametry oczekiwane przez tę klasę. Demonstruje to listing 12.4.

Listing 12.4. Przeciążone konstruktory w wyprowadzonych klasach

0: //Listing 12.4 Przeciążone konstruktory w wyprowadzonych klasach

1:

2: #include <iostream>

3: using namespace std;

4:

5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

6:

7: class Mammal

8: {

9: public:

10: // konstruktory

11: Mammal();

12: Mammal(int age);

13: ~Mammal();

14:

15: //akcesory

16: int GetAge() const { return itsAge; }

17: void SetAge(int age) { itsAge = age; }

18: int GetWeight() const { return itsWeight; }

19: void SetWeight(int weight) { itsWeight = weight; }

20:

21: // inne metody

22: void Speak()const { cout << "Dzwiek ssaka!\n"; }

23: void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; }

24:

25:

26: protected:

27: int itsAge;

28: int itsWeight;

29: };

30:

31: class Dog : public Mammal

32: {

33: public:

34:

35: // konstruktory

36: Dog();

37: Dog(int age);

38: Dog(int age, int weight);

39: Dog(int age, BREED breed);

40: Dog(int age, int weight, BREED breed);

41: ~Dog();

42:

43: // akcesory

44: BREED GetBreed() const { return itsBreed; }

45: void SetBreed(BREED breed) { itsBreed = breed; }

46:

47: // inne metody

48: void WagTail() const { cout << "Macham ogonem...\n"; }

49: void BegForFood() const { cout << "Prosze o jedzenie...\n"; }

50:

51: private:

52: BREED itsBreed;

53: };

54:

55: Mammal::Mammal():

56: itsAge(1),

57: itsWeight(5)

58: {

59: cout << "Konstruktor klasy Mammal...\n";

60: }

61:

62: Mammal::Mammal(int age):

63: itsAge(age),

64: itsWeight(5)

65: {

66: cout << "Konstruktor klasy Mammal(int)...\n";

67: }

68:

69: Mammal::~Mammal()

70: {

71: cout << "Destruktor klasy Mammal...\n";

72: }

73:

74: Dog::Dog():

75: Mammal(),

76: itsBreed(GOLDEN)

77: {

78: cout << "Konstruktor klasy Dog...\n";

79: }

80:

81: Dog::Dog(int age):

82: Mammal(age),

83: itsBreed(GOLDEN)

84: {

85: cout << "Konstruktor klasy Dog(int)...\n";

86: }

87:

88: Dog::Dog(int age, int weight):

89: Mammal(age),

90: itsBreed(GOLDEN)

91: {

92: itsWeight = weight;

93: cout << "Konstruktor klasy Dog(int, int)...\n";

94: }

95:

96: Dog::Dog(int age, int weight, BREED breed):

97: Mammal(age),

98: itsBreed(breed)

99: {

100: itsWeight = weight;

101: cout << "Konstruktor klasy Dog(int, int, BREED)...\n";

102: }

103:

104: Dog::Dog(int age, BREED breed):

105: Mammal(age),

106: itsBreed(breed)

107: {

108: cout << "Konstruktor klasy Dog(int, BREED)...\n";

109: }

110:

111: Dog::~Dog()

112: {

113: cout << "Destruktor klasy Dog...\n";

114: }

115: int main()

116: {

117: Dog fido;

118: Dog rover(5);

119: Dog buster(6,8);

120: Dog yorkie (3,GOLDEN);

121: Dog dobbie (4,20,DOBERMAN);

122: fido.Speak();

123: rover.WagTail();

124: cout << "Yorkie ma " << yorkie.GetAge() << " lat(a)\n";

125: cout << "Dobbie wazy ";

126: cout << dobbie.GetWeight() << " funtow\n";

127: return 0;

128: }

UWAGA Linie wyniku zostały ponumerowane,[Author ID2: at Wed Nov 21 09:37:00 2001 ] tak,[Author ID2: at Wed Nov 21 09:37:00 2001 ] aby można się było do nich odwoływać w analizie.

Wynik:[Author ID2: at Wed Nov 21 09:37:00 2001 ]

1: Konstruktor klasy Mammal...

2: Konstruktor klasy Dog...

3: Konstruktor klasy Mammal(int)...

4: Konstruktor klasy Dog(int)...

5: Konstruktor klasy Mammal(int)...

6: Konstruktor klasy Dog(int, int)...

7: Konstruktor klasy Mammal(int)...

8: Konstruktor klasy Dog(int, BREED)...

9: Konstruktor klasy Mammal(int)...

10: Konstruktor klasy Dog(int, int, BREED)...

11: Dzwiek ssaka!

12: Macham ogonem...

13: Yorkie ma 3 lat(a)

14: Dobbie wazy 20 funtow

15: Destruktor klasy Dog...

16: Destruktor klasy Mammal...

17: Destruktor klasy Dog...

18: Destruktor klasy Mammal...

19: Destruktor klasy Dog...

20: Destruktor klasy Mammal...

21: Destruktor klasy Dog...

22: Destruktor klasy Mammal...

23: Destruktor klasy Dog...

24: Destruktor klasy Mammal...

Analiza:[Author ID2: at Wed Nov 21 09:38:00 2001 ]

Na listingu 12.4, w linii 12[Author ID1: at Wed Oct 31 11:32:00 2001 ].[Author ID2: at Wed Nov 21 09:38:00 2001 ]1[Author ID1: at Wed Oct 31 11:32:00 2001 ], konstruktor klasy Mammal został przeciążony tak,[Author ID2: at Wed Nov 21 09:38:00 2001 ] że przyjmuje wartość całkowitą,[Author ID2: at Wed Nov 21 09:38:00 2001 ] określającą wiek ssaka. Implementacja w liniach od 62.[Author ID2: at Wed Nov 21 09:38:00 2001 ] do 67.[Author ID2: at Wed Nov 21 09:38:00 2001 ] inicjalizuje składową itsAge wartością przekazaną do tego konstruktora, zaś składową itsWeight inicjalizuje za pomocą [Author ID2: at Wed Nov 21 09:38:00 2001 ]wartością[Author ID2: at Wed Nov 21 09:38:00 2001 ] 5.

Klasa Dog posiada pięć przeciążonych konstruktorów, zadeklarowanych w liniach od 36.[Author ID2: at Wed Nov 21 09:38:00 2001 ] do 40. Pierwszym z nich jest konstruktor domyślny. Drugi konstruktor otrzymuje wiek, będący tym samym parametrem,[Author ID2: at Wed Nov 21 09:38:00 2001 ] co parametr konstruktora klasy Mammal. Trzeci konstruktor otrzymuje zarówno wiek,[Author ID2: at Wed Nov 21 09:38:00 2001 ] jak i wagę, czwarty otrzymuje wiek i rasę, zaś piąty otrzymuje wiek, wagę oraz rasę.

Zwróć uwagę na linię 75.[Author ID2: at Wed Nov 21 09:39:00 2001 ], w której domyślny konstruktor klasy Dog wywołuje domyślny konstruktor klasy Mammal. Choć wywołanie to [Author ID2: at Wed Nov 21 09:39:00 2001 ]nie jest to bezwzględnie wymagane[Author ID2: at Wed Nov 21 09:39:00 2001 ]konieczne[Author ID2: at Wed Nov 21 09:39:00 2001 ], służy jako [Author ID2: at Wed Nov 21 09:39:00 2001 ]dokumentacja[Author ID2: at Wed Nov 21 09:39:00 2001 ]uje,[Author ID2: at Wed Nov 21 09:39:00 2001 ] że mieliśmy zamiar wywołać konstruktor bazowy,[Author ID2: at Wed Nov 21 09:39:00 2001 ] nie posiadający żadnych parametrów. Konstruktor bazowy zostały[Author ID2: at Wed Nov 21 09:39:00 2001 ]by[Author ID2: at Wed Nov 21 09:39:00 2001 ] wywołany w każdym przypadku, ale w ten sposób wyraźniej przedstawiamy nasze intencje.

Implementacja konstruktora klasy Dog [Author ID2: at Wed Nov 21 09:40:00 2001 ], [Author ID2: at Wed Nov 21 09:40:00 2001 ]przyjmującego wartość całkowitą znajduje się w liniach od 81.[Author ID2: at Wed Nov 21 09:40:00 2001 ] do 86. W swojej fazie inicjalizacji (linie 82.[Author ID2: at Wed Nov 21 09:40:00 2001 ] i 83.[Author ID2: at Wed Nov 21 09:40:00 2001 ]), obiekt Dog inicjalizuje swoją klasę bazową, przekazując jej parametr, po czym inicjalizuje swoją rasę.

Inny konstruktor klasy Dog jest zawarty w liniach od 88.[Author ID2: at Wed Nov 21 09:40:00 2001 ] do 94. Ten konstruktor otrzymuje dwa parametry. Także w tym przypadku inicjalizuje on [Author ID1: at Wed Oct 31 11:33:00 2001 ]swoją klasę bazową,[Author ID2: at Wed Nov 21 09:40:00 2001 ] wywołując jej odpowiedni konstruktor, lecz tym razem dodatkowo przypisuje wartość zmiennej itsWeight w klasie bazowej. Zauważ,[Author ID2: at Wed Nov 21 09:40:00 2001 ] że nie można tu [Author ID1: at Wed Oct 31 11:33:00 2001 ]przypisywać wartości zmiennym klasy bazowej w fazie inicjalizacji. Ponieważ klasa Mammal nie posiada konstruktora przyjmującego ten parametr, musimy uczynić to wewnątrz ciała konstruktora klasy Dog.

Przejrzyj pozostałe konstruktory,[Author ID2: at Wed Nov 21 09:40:00 2001 ] aby upewnić się,[Author ID2: at Wed Nov 21 09:40:00 2001 ] że pojąłeś [Author ID2: at Wed Nov 21 09:40:00 2001 ]zrozumiałeś [Author ID2: at Wed Nov 21 09:40:00 2001 ]sposób ich działania. Zwróć uwagę,[Author ID2: at Wed Nov 21 09:40:00 2001 ] co jest inicjalizowane, a co musi poczekać na przejście do ciała konstruktora.

Linie wyników działania tego programu zostały ponumerowane tak[Author ID2: at Wed Nov 21 09:41:00 2001 ], aby można się [Author ID2: at Wed Nov 21 09:41:00 2001 ]było odwoływać się [Author ID2: at Wed Nov 21 09:41:00 2001 ]do nich podczas analizy. Pierwsze dwie linie wyników reprezentują tworzenie obiektu fido, za[Author ID2: at Wed Nov 21 09:41:00 2001 ] użyciem [Author ID2: at Wed Nov 21 09:41:00 2001 ]pomocą [Author ID2: at Wed Nov 21 09:41:00 2001 ]domyślnego konstruktora.

Linie 3.[Author ID2: at Wed Nov 21 09:41:00 2001 ] i 4.[Author ID2: at Wed Nov 21 09:41:00 2001 ] wyników reprezentują tworzenie obiektu rover. Linie 5.[Author ID2: at Wed Nov 21 09:41:00 2001 ] i 6.[Author ID2: at Wed Nov 21 09:41:00 2001 ] odnoszą się do obiektu buster. Zwróć uwagę,[Author ID2: at Wed Nov 21 09:42:00 2001 ] że wywoływany konstruktor klasy Mammal jest konstruktorem przyjmującym jedną [Author ID1: at Wed Oct 31 11:33:00 2001 ]wartość całkowitą, mimo iż wywoływanym konstruktorem klasy Dog jest konstruktor przyjmujący dwie wartości całkowite.

Po stworzeniu w[Author ID2: at Wed Nov 21 09:42:00 2001 ]W[Author ID2: at Wed Nov 21 09:42:00 2001 ]szystkich[Author ID2: at Wed Nov 21 09:42:00 2001 ]e stworzone[Author ID2: at Wed Nov 21 09:42:00 2001 ] obiekty [Author ID1: at Wed Oct 31 11:34:00 2001 ]zostają [Author ID1: at Wed Oct 31 11:34:00 2001 ]używane [Author ID1: at Wed Oct 31 11:34:00 2001 ]te[Author ID1: at Wed Oct 31 11:34:00 2001 ] w programie, po czym [Author ID1: at Wed Oct 31 11:34:00 2001 ]i [Author ID1: at Wed Oct 31 11:34:00 2001 ]wychodzą poza zakres. Podczas niszczenia każdego obiektu najpierw wywoływany jest destruktor klasy Dog, a następnie destruktor klasy Mammal (łącznie po pięć razy).

Przesłanianie funkcji

Obiekt klasy Dog posiada dostęp do wszystkich funkcji składowych klasy Mammal, a także do wszystkich funkcji składowych, na przykład takich jak WagTail(), które mogłyby zostać dodane w [Author ID1: at Wed Oct 31 11:35:00 2001 ]przez[Author ID1: at Wed Oct 31 11:35:00 2001 ] deklaracji[Author ID1: at Wed Oct 31 11:35:00 2001 ]ę[Author ID1: at Wed Oct 31 11:35:00 2001 ] klasy Dog. Może także przesłaniać funkcje klasy bazowej. Przesłonięcie (ang. override) funkcji oznacza zmianę implementacji funkcji klasy bazowej w klasie z niej wyprowadzonej. Gdy tworzysz obiekt klasy wyprowadzonej, wywoływana jest właściwa funkcja.

Gdy klasa wyprowadzona tworzy funkcję o tym samym zwracanym typie i sygnaturze, co funkcja składowa w klasie bazowej, ale z inną implementacją, mówimy, że ta[Author ID1: at Wed Oct 31 11:36:00 2001 ]funkcja klasy bazowej [Author ID1: at Wed Oct 31 11:36:00 2001 ]została przesłonięta.

Gdy przesłaniasz funkcję, jej sygnatura musi się zgadzać z sygnaturą tej funkcji w klasie bazowej. Sygnatura jest innym prototypem funkcji niż typ zwracany; zawiera nazwę, listę parametrów oraz słowo kluczowe const (o ile jest używane). Zwracane typy mogą się od siebie różnić.

Listing 12.5 pokazuje, co się stanie, gdy w klasie Dog przesłonimy metodę Speak() klasy Mammal. Dla zaoszczędzenia miejsca, z tych klas zostały usunięte akcesory.

Listing 12.5. Przesłanianie metod klasy bazowej w klasie potomnej

0: //Listing 12.5 Przesłanianie metod klasy bazowej w klasie potomnej

1:

2: #include <iostream>

3: using std::cout;

4:

5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

6:

7: class Mammal

8: {

9: public:

10: // konstruktory

11: Mammal() { cout << "Konstruktor klasy Mammal...\n"; }

12: ~Mammal() { cout << "Destruktor klasy Mammal...\n"; }

13:

14: // inne metody

15: void Speak()const { cout << "Dzwiek ssaka!\n"; }

16: void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; }

17:

18:

19: protected:

20: int itsAge;

21: int itsWeight;

22: };

23:

24: class Dog : public Mammal

25: {

26: public:

27:

28: // konstruktory

29: Dog(){ cout << "Konstruktor klasy Dog...\n"; }

30: ~Dog(){ cout << "Destruktor klasy Dog...\n"; }

31:

32: // inne metody

33: void WagTail() const { cout << "Macham ogonkiem...\n"; }

34: void BegForFood() const { cout << "Prosze o jedzenie...\n"; }

35: void Speak() const { cout << "Hau!\n"; }

36:

37: private:

38: BREED itsBreed;

39: };

40:

41: int main()

42: {

43: Mammal bigAnimal;

44: Dog fido;

45: bigAnimal.Speak();

46: fido.Speak();

47: return 0;

48: }

Wynik

Konstruktor klasy Mammal...

Konstruktor klasy Mammal...

Konstruktor klasy Dog...

Dzwiek ssaka!

Hau!

Destruktor klasy Dog...

Destruktor klasy Mammal...

Destruktor klasy Mammal...

Analiza

W linii 35.,[Author ID1: at Wed Oct 31 11:36:00 2001 ] w[Author ID1: at Wed Oct 31 11:36:00 2001 ] klasa[Author ID1: at Wed Oct 31 11:36:00 2001 ]ie[Author ID1: at Wed Oct 31 11:36:00 2001 ] Dog zostaje[Author ID1: at Wed Oct 31 11:36:00 2001 ] przesłania [Author ID1: at Wed Oct 31 11:36:00 2001 ]onięta[Author ID1: at Wed Oct 31 11:36:00 2001 ] metodę[Author ID1: at Wed Oct 31 11:37:00 2001 ]a[Author ID1: at Wed Oct 31 11:37:00 2001 ] Speak(),[Author ID1: at Wed Oct 31 11:37:00 2001 ] z[Author ID1: at Wed Oct 31 11:37:00 2001 ] klasy [Author ID1: at Wed Oct 31 11:37:00 2001 ]Mammal[Author ID1: at Wed Oct 31 11:37:00 2001 ], [Author ID1: at Wed Oct 31 11:37:00 2001 ]co powoduje, że obiekty klasy Dog w momencie wywołania tej metody wypisują komunikat Hau!. W linii 43. tworzony jest obiekt klasy Mammal, o nazwie bigAnimal (duże zwierzę), powodując wypisanie pierwszej linii wyników (w efekcie wywołania konstruktora klasy Mammal). W linii 44. tworzony jest obiekt klasy Dog, o nazwie fido, powodując wypisanie dwóch następnych linii wyników (powstających w efekcie wywołania konstruktora klasy Mammal, a następnie konstruktora klasy Dog).

W linii 45. obiekt klasy Mammal wywołuje swoją metodę Speak(), zaś w linii 46. swoją metodę Speak() wywołuje obiekt klasy Dog. Jak widać w wyniku[Author ID1: at Wed Oct 31 11:38:00 2001 ]na wydruku[Author ID1: at Wed Oct 31 11:38:00 2001 ], wywoływ[Author ID1: at Wed Oct 31 11:38:00 2001 ]ane [Author ID1: at Wed Oct 31 11:38:00 2001 ]zostały[Author ID1: at Wed Oct 31 11:38:00 2001 ] odpowiednie metody z [Author ID1: at Wed Oct 31 11:38:00 2001 ]obu klas. Na zakończenie, oba obiekty wychodzą z zakresu, więc są wywoływane ich destruktory.

Przesłanianie a przeciążanie

Te określenia są do siebie podobne i odnoszą się do podobnych rzeczy. Gdy przeciążasz metodę, tworzysz kilka funkcji o tej samej nazwie, ale z innymi sygnaturami. Gdy przesłaniasz metodę, tworzysz metodę w klasie wyprowadzonej; posiada ona taką samą nazwę i sygnaturę, jak przesłaniana metoda w klasie bazowej.

Ukrywanie metod klasy bazowej

W poprzednim listingu metoda Speak() klasy Dog ukryła metodę klasy bazowej. Właśnie tego wtedy potrzebowaliśmy, ale w innych sytuacjach metoda ta może dawać nieoczekiwane rezultaty. Gdyby klasa Mammal posiadała przeciążoną metodę Move() (ruszaj), zaś klasa Dog przesłoniłaby tę metodę, wtedy metoda klasy Dog ukryłaby w klasie Mammal wszystkie metody o tej nazwie.

Jeśli klasa Mammal posiada trzy przeciążone metody o nazwie Move()jedną [Author ID1: at Wed Oct 31 11:44:00 2001 ]bez parametrów, drugą [Author ID1: at Wed Oct 31 11:45:00 2001 ]z parametrem w postaci liczby [Author ID1: at Wed Oct 31 11:45:00 2001 ]wartości[Author ID1: at Wed Oct 31 11:45:00 2001 ] całkowitej oraz trzecią [Author ID1: at Wed Oct 31 11:45:00 2001 ]z parametrem całkowitym i kierunkowym [Author ID1: at Wed Oct 31 11:46:00 2001 ]iem[Author ID1: at Wed Oct 31 11:46:00 2001 ] — zaś klasa Dog przesłania jedynie metodę Move() bez parametrów, wtedy dostęp do pozostałych dwóch metod poprzez obiekt klasy Dog nie będzie [Author ID1: at Wed Oct 31 11:46:00 2001 ]jest[Author ID1: at Wed Oct 31 11:46:00 2001 ] łatwy. Problem ten ilustruje listing 12.6.

Listing 12.6. Ukrywanie metod

0: //Listing 12.6 Ukrywanie metod

1:

2: #include <iostream>

3:

4:

5: class Mammal

6: {

7: public:

8: void Move() const { std::cout << "Mammal przeszedl jeden krok\n"; }

9: void Move(int distance) const

10: {

11: std::cout << "Mammal przeszedl ";

12: std::cout << distance <<" kroki.\n";

13: }

14: protected:

15: int itsAge;

16: int itsWeight;

17: };

18:

19: class Dog : public Mammal

20: {

21: public:

22: // Możesz zauważyć ostrzeżenie, że ukrywasz funkcję!

23: void Move() const { std::cout << "Dog przeszedl 5 krokow.\n"; }

24: };

25:

26: int main()

27: {

28: Mammal bigAnimal;

29: Dog fido;

30: bigAnimal.Move();

31: bigAnimal.Move(2);

32: fido.Move();

33: // fido.Move(10);

34: return 0;

35: }

Wynik

Mammal przeszedl jeden krok

Mammal przeszedl 2 kroki.

Dog przeszedl 5 krokow.

Analiza

Z tych klas zostały usunięte wszystkie dodatkowe metody i dane. W liniach 8. i 9. klasa Mammal deklaruje przeciążone metody Move(). W linii 23. klasa Dog przesłania wersję metody Move(), która nie posiada żadnych parametrów. Metody te są wywoływane w liniach od 30. do 32., zaś w wynikach widzimy, że zostały one wykonane.

Linia 33. została jednak wykomentowana, gdyż powoduje powstanie błędu kompilacji. Choć[Author ID1: at Wed Oct 31 11:49:00 2001 ] k[Author ID1: at Wed Oct 31 11:49:00 2001 ] K[Author ID1: at Wed Oct 31 11:49:00 2001 ]lasa Dog mogłaby wywoływać metodę Move(int), gdyby nie przesłoniła wersji metody Move() bez parametrów.[Author ID1: at Wed Oct 31 11:49:00 2001 ],[Author ID1: at Wed Oct 31 11:49:00 2001 ] jednak p[Author ID1: at Wed Oct 31 11:50:00 2001 ]P[Author ID1: at Wed Oct 31 11:50:00 2001 ]onieważ jednak [Author ID1: at Wed Oct 31 11:50:00 2001 ]metoda ta została przesłonięta, w[Author ID1: at Wed Oct 31 11:52:00 2001 ] tym celu[Author ID1: at Wed Oct 31 11:52:00 2001 ]konieczne jest przesłonięcie obu wersji. W przeciwnym razie metody, które nie zostały przesłonięte, są ukrywane. Przypomina to regułę, według której w momencie dostarczenia przez nas jakiegokolwiek konstruktora, kompilator nie dostarcza już konstruktora domyślnego.

Oto obowiązująca reguła: Gdy przesłonisz jakąkolwiek z przeciążonych metod, wszystkie inne przeciążenia tej metody zostają ukryte. Jeśli nie chcesz ich ukrywać, musisz przesłonić je wszystkie.

Ukrycie metody klasy bazowej, gdy chcemy ją przesłonić, jest dość częstym błędem; wynika on z tego, że nie zastosowaliśmy słowa kluczowego const. Słowo kluczowe const stanowi część sygnatury i pominięcie go powoduje zmianę sygnatury, a więc ukrycie metody, a nie przesłonięcie jej.

Przesłanianie a ukrywanie

W następnym podrozdziale zajmiemy się metodami wirtualnymi. Przesłonięcie metody wirtualnej umożliwia uzyskanie polimorfizmu, zaś ukrycie jej uniemożliwia polimorfizm. Więcej informacji na ten temat uzyskasz już wkrótce.

Wywoływanie metod klasy bazowej

Jeśli przesłoniłeś metodę klasy bazowej, wciąż możesz ją wywoływać, używając pełnej kwalifikowanej nazwy metody. W tym celu zapisuje się nazwę klasy bazowej, dwa dwukropki oraz nazwę metody. Na przykład: Mammal::Move().

Istnieje możliwość przepisania linii 33. z listingu 12.6 tak, aby można ją było skompilować:

33: fido.Mammal::Move(10);

Taka linia wywołuje metodę klasy Mammal w sposób jawny. Proces ten w pełni ilustruje listing 12.7.

Listing 12.7. Wywoływanie metody bazowej z metody przesłoniętej

0: //Listing 12.7 Wywoływanie metody bazowej z metody przesłoniętej

1:

2: #include <iostream>

3: using namespace std;

4:

5: class Mammal

6: {

7: public:

8: void Move() const { cout << "Mammal przeszedl jeden krok\n"; }

9: void Move(int distance) const

10: {

11: cout << "Mammal przeszedl " << distance;

12: cout << " kroki.\n";

13: }

14:

15: protected:

16: int itsAge;

17: int itsWeight;

18: };

19:

20: class Dog : public Mammal

21: {

22: public:

23: void Move()const;

24:

25: };

26:

27: void Dog::Move() const

28: {

29: cout << "W metodzie Move klasy dog...\n";

30: Mammal::Move(3);

31: }

32:

33: int main()

34: {

35: Mammal bigAnimal;

36: Dog fido;

37: bigAnimal.Move(2);

38: fido.Mammal::Move(6);

39: return 0;

40: }

Wynik

Mammal przeszedl 2 kroki.

Mammal przeszedl 6 kroki.

Analiza

W linii 35. tworzony jest obiekt bigAnimal klasy Mammal, zaś w linii 36. jest tworzony obiekt fido klasy Dog. Metoda wywoływana w linii 37. wykonuje metodę Move() klasy Mammal, przyjmującą pojedynczy argument typu int.

Programista chciał wywołać metodę Move(int) obiektu klasy Dog, ale miał problem. W klasie Dog została przesłonięta metoda Move(), lecz nie została przesłonięta wersja tej metody z parametrem typu int. Rozwiązał ten problem, jawnie wywołując metodę Move(int) klasy bazowej, w linii 38.

TAK

NIE

Zmieniaj zachowanie określonych funkcji w klasie wyprowadzonej przez przesłanianie metod klasy bazowej.

Nie ukrywaj metod klasy bazowej przez zmianę sygnatury metod w klasie pochodnej.

Metody wirtualne

W tym rozdziale sygnalizujemy fakt, iż obiekt klasy Dog jest obiektem klasy Mammal. Jak dotąd oznaczało to jedynie, że obiekt Dog dziedziczy atrybuty (dane) oraz możliwości (metody) swojej klasy bazowej. Jednak w języku C++ relacja jest-czymś sięga nieco głębiej.

C++ rozszerza swój polimorfizm, pozwalając, by wskaźnikom do klas bazowych przypisywane były wskaźniki do obiektów klas pochodnych. Zatem można napisać:

Mammal* pMammal = new Dog;

W ten sposób tworzymy na stercie nowy obiekt klasy Dog, zaś otrzymany do [Author ID1: at Wed Oct 31 12:04:00 2001 ]niego wskaźnik przypisujemy wskaźnikowi do obiektów [Author ID1: at Wed Oct 31 12:04:00 2001 ]klasy Mammal. Jest to poprawne, gdyż pies (ang. dog) jest ssakiem (ang. mammal).

UWAGA Na tym właśnie polega polimorfizm. Na przykład, możesz stworzyć wiele rodzajów okien, np. okna dialogowe, okna przewijane lub listy, każdemu z nich przydzielając wirtualną metodę draw() (rysuj). Tworząc wskaźnik do okna i przypisując okna dialogowe i inne wyprowadzone typy temu wskaźnikowi, możesz wywoływać metodę draw(); bez względu na bieżący typ obiektu, na który on wskazuje. Za każdym zostanie wywołania właściwa funkcja draw().

Możesz użyć tego wskaźnika do wywołania dowolnej metody klasy Mammal. Z pewnością jednak bardziej spodoba ci się, że zostaną wywołane właściwe metody [Author ID1: at Wed Oct 31 12:07:00 2001 ]przesłaniające [Author ID1: at Wed Oct 31 12:07:00 2001 ]onięte metody [Author ID1: at Wed Oct 31 12:07:00 2001 ] [Author ID1: at Wed Oct 31 12:08:00 2001 ]z [Author ID1: at Wed Oct 31 12:07:00 2001 ]klasy Dog. Proces ten umożliwiają funkcje wirtualne. Mechanizm ten ilustruje listing 12.8, pokazuje on także, co się dzieje z metodami, które nie są wirtualne.

Listing 12.8. Używanie metod wirtualnych

0: //Listing 12.8 Używanie metod wirtualnych

1:

2: #include <iostream>

3: using std::cout;

4:

5: class Mammal

6: {

7: public:

8: Mammal():itsAge(1) { cout << "Konstruktor klasy Mammal...\n"; }

9: virtual ~Mammal() { cout << "Destruktor klasy Mammal...\n"; }

10: void Move() const { cout << "Mammal przeszedl jeden krok\n"; }

11: virtual void Speak() const { cout << "Metoda Speak klasy Mammal\n"; }

12: protected:

13: int itsAge;

14:

15: };

16:

17: class Dog : public Mammal

18: {

19: public:

20: Dog() { cout << "Konstruktor klasy Dog...\n"; }

21: virtual ~Dog() { cout << "Destruktor klasy Dog...\n"; }

22: void WagTail() { cout << "Macham ogonkiem...\n"; }

23: void Speak()const { cout << "Hau!\n"; }

24: void Move()const { cout << "Dog przeszedl 5 krokow...\n"; }

25: };

26:

27: int main()

28: {

29:

30: Mammal *pDog = new Dog;

31: pDog->Move();

32: pDog->Speak();

33:

34: return 0;

35: }

Wynik

Konstruktor klasy Mammal...

Konstruktor klasy Dog...

Mammal przeszedl jeden krok

Hau!

Analiza

W linii 11. została zadeklarowana wirtualna metoda klasy Mammal — metoda Speak(). Projektant tej klasy sygnalizuje w ten sposób, że oczekuje, iż ta klasa może być typem bazowym innej klasy. Klasa pochodna najprawdopodobniej zechce przesłonić tę funkcję.

W linii 30. tworzony jest wskaźnik (pDog) do klasy Mammal, lecz jest mu przypisywany adres nowego obiektu klasy Dog. Ponieważ obiekt klasy Dog jest obiektem klasy Mammal, przypisanie to jest poprawne. Następnie wskaźnik ten jest używany do wywołania funkcji Move(). Ponieważ kompilator wie tylko, że pDog jest wskaźnikiem do klasy Mammal, zagląda do obiektu tej klasy w celu znalezienia metody Move().

W linii 32. za pomocą tego wskaźnika zostaje wywołana metoda Speak(). Ponieważ metoda ta jest metodą wirtualną, wywołana zostaje przesłonięta [Author ID1: at Wed Oct 31 12:09:00 2001 ]metoda Speak() przesłonięta [Author ID1: at Wed Oct 31 12:09:00 2001 ]w[Author ID1: at Wed Oct 31 12:11:00 2001 ] z [Author ID1: at Wed Oct 31 12:11:00 2001 ]klasy [Author ID1: at Wed Oct 31 12:11:00 2001 ]ie[Author ID1: at Wed Oct 31 12:11:00 2001 ] Dog.

To prawie magia. Funkcja wywołująca wie tylko, iż posiada wskaźnik do obiektu klasy Mammal, a mimo to zostaje wywołana metoda klasy Dog. W rzeczywistości, gdybyśmy mieli tablicę wskaźników do klasy Mammal, a każdy z nich wskazywałby obiekt klasy wyprowadzonej z tej klasy, moglibyśmy wywoływać jej[Author ID1: at Wed Oct 31 12:11:00 2001 ] po kolei, a wywoływ[Author ID1: at Wed Oct 31 12:12:00 2001 ]ane funkcje zostałyby[Author ID1: at Wed Oct 31 12:12:00 2001 ]byłyby[Author ID1: at Wed Oct 31 12:12:00 2001 ] właściwe. Proces ten ilustruje listing 12.9.

Listing 12.9. Wywoływanie wielu funkcji wirtualnych

0: //Listing 12.9 Wywoływanie wielu funkcji wirtualnych

1:

2: #include <iostream>

3: using namespace std;

4:

5: class Mammal

6: {

7: public:

8: Mammal():itsAge(1) { }

9: virtual ~Mammal() { }

10: virtual void Speak() const { cout << "Ssak mowi!\n"; }

11: protected:

12: int itsAge;

13: };

14:

15: class Dog : public Mammal

16: {

17: public:

18: void Speak()const { cout << "Hau!\n"; }

19: };

20:

21:

22: class Cat : public Mammal

23: {

24: public:

25: void Speak()const { cout << "Miau!\n"; }

26: };

27:

28:

29: class Horse : public Mammal

30: {

31: public:

32: void Speak()const { cout << "Ihaaa!\n"; }

33: };

34:

35: class Pig : public Mammal

36: {

37: public:

38: void Speak()const { cout << "Kwik!\n"; }

39: };

40:

41: int main()

42: {

43: Mammal* theArray[5];

44: Mammal* ptr;

45: int choice, i;

46: for ( i = 0; i<5; i++)

47: {

48: cout << "(1)pies (2)kot (3)kon (4)swinia: ";

49: cin >> choice;

50: switch (choice)

51: {

52: case 1: ptr = new Dog;

53: break;

54: case 2: ptr = new Cat;

55: break;

56: case 3: ptr = new Horse;

57: break;

58: case 4: ptr = new Pig;

59: break;

60: default: ptr = new Mammal;

61: break;

62: }

63: theArray[i] = ptr;

64: }

65: for (i=0;i<5;i++)

66: theArray[i]->Speak();

67: return 0;

68: }

Wynik

(1)pies (2)kot (3)kon (4)swinia: 1

(1)pies (2)kot (3)kon (4)swinia: 2

(1)pies (2)kot (3)kon (4)swinia: 3

(1)pies (2)kot (3)kon (4)swinia: 4

(1)pies (2)kot (3)kon (4)swinia: 5

Hau!

Miau!

Ihaaa!

Kwik!

Ssak mowi!

Analiza

Ten skrócony program, w którym pozostawiono jedynie najistotniejsze części każdej z klas, przedstawia funkcje wirtualne w ich najczystszej postaci. Zadeklarowane zostały cztery klasy: Dog, Cat, Horse (koń) oraz Pig (świnia), wszystkie wyprowadzone z klasy Mammal.

W linii 10. funkcja Speak() klasy Mammal została zadeklarowana jako metoda wirtualna. Metoda ta zostaje przesłonięta we wszystkich czterech klasach pochodnych, w liniach 18., 25., 32. i 38. Użytkownik jest proszony o wybranie obiektu, który ma zostać stworzony, po czym w liniach od 47. do 64[Author ID1: at Wed Oct 31 12:15:00 2001 ].5[Author ID1: at Wed Oct 31 12:15:00 2001 ] do tablicy zostają dodane wskaźniki.

UWAGA W czasie kompilacji nie ma możliwości sprawdzenia, które obiekty zostaną stworzone[Author ID1: at Wed Oct 31 12:15:00 2001 ]y[Author ID1: at Wed Oct 31 12:15:00 2001 ] i które wersje [Author ID1: at Wed Oct 31 12:15:00 2001 ]metody Speak() mają być[Author ID1: at Wed Oct 31 12:15:00 2001 ]zostaną[Author ID1: at Wed Oct 31 12:15:00 2001 ] wywołane. Wskaźnik ptr jest przypisywany do obiektu już podczas wykonywania programu. Nazywa się to wiązaniem dynamicznym (dokonywanym podczas działania programu), w przeciwieństwie do wiązania statycznego (dokonywanego podczas kompilacji).

Często zadawane pytanie

Jeśli oznaczę metodę składową jako wirtualną w klasie bazowej, to czy muszę oznaczać ją jako wirtualną także w klasach pochodnych?

Odpowiedź: Nie, jeśli oznaczysz metodę jako wirtualną, a potem przesłonisz ją w klasie pochodnej, pozostanie ona w dalszym ciągu wirtualna. Jednak warto oznaczyć ją jako wirtualną (choć nie jest to konieczne) — dzięki temu kod jest łatwiejszy do zrozumienia.

Jak działają funkcje wirtualne

Gdy tworzony jest obiekt klasy pochodnej, na przykład obiekt klasy Dog, najpierw wywoływany jest konstruktor klasy bazowej, a następnie konstruktor klasy pochodnej. Rysunek 12.2 pokazuje, jak wygląda obiekt klasy Dog po utworzeniu go. Zwróć uwagę,[Author ID1: at Wed Oct 31 12:16:00 2001 ] że część Mammal obiektu jest w pamięci spójna z częścią Dog.

Rys. 12.2. Obiekt klasy Dog po utworzeniu

0x01 graphic

Gdy w obiekcie tworzona jest funkcja wirtualna, jest ona śledzona przez ten obiekt. Wiele kompilatorów buduje tablicę funkcji wirtualnych, nazywaną v-table. Dla każdego typu przechowywana jest jedna taka tablica, zaś każdy obiekt tego typu przechowuje do niej wskaźnik (nazywany vptr lub v-pointer).

Choć implementacje różnią się od siebie, wszystkie kompilatory muszą wykonywać te same czynności, dlatego przedstawiony poniżej opis nie będzie odbiegał od rzeczywistości.

Wskaźnik vptr każdego z obiektów wskazuje na tablicę v-table, która z kolei zawiera wskaźniki do każdej z funkcji wirtualnych. (Uwaga: wskaźniki do funkcji zostaną szerzej omówione w rozdziale 15., „Specjalne klasy i funkcje”). Gdy tworzona jest część Mammal klasy Dog, wskaźnik vptr jest inicjalizowany tak, by wskazywał właściwą część tablicy v-table, jak pokazano na rysunku 12.3.

Rys. 12.3. V-table klasy Mammal

0x01 graphic

Gdy zostaje wywołany konstruktor klasy Dog i dodawana jest część Dog tego obiektu, wskaźnik vptr jest modyfikowany tak, by wskazywał na przesłonięcia funkcji wirtualnych (o ile istnieją) w klasie Dog (patrz rysunek 12.4).

Rys. 12.4. V-table klasy Dog

0x01 graphic

Gdy używany jest wskaźnik do klasy Mammal, wskaźnik vptr cały czas wskazuje właściwą funkcję, w zależności od „rzeczywistego” typu obiektu. W chwili wywołania metody Speak() wywoływana jest właściwa funkcja.

Nie możesz przejść stamt[Author ID1: at Wed Oct 31 12:24:00 2001 ]ąd dotąd tu[Author ID1: at Wed Oct 31 12:21:00 2001 ]

Gdyby obiekt klasy Dog miał metodę (na przykład WagTail()), która nie[Author ID1: at Wed Oct 31 12:26:00 2001 ]w[Author ID1: at Wed Oct 31 12:26:00 2001 ] występowałaby w klasie Mammal, w celu odwołania się do tej metody nie mógłbyś użyć wskaźnika do klasy Mammal (chyba że jawnie rzutowałbyś ten wskaźnik do klasy Dog). Ponieważ WagTail() nie jest funkcją wirtualną i ponieważ nie występuje ona w klasie Mammal, nie możesz jej użyć, nie posiadając obiektu klasy Dog lub wskaźnika do klasy Dog.

Choć możesz przekształcić wskaźnik do klasy Mammal we wskaźnik do klasy Dog, istnieją dużo lepsze i bezpieczniejsze sposoby wywołania metody WagTail(). C++ odradza jawne rzutowanie (konwersję) [Author ID1: at Wed Oct 31 12:26:00 2001 ]typów, gdyż jest ono podatne na błędy. Ten problem zostanie omówiony przy okazji rozważań na temat wielokrotnego dziedziczenia w rozdziale 15. oraz przy omawianiu szablonów w rozdziale 20., „Wyjątki i obsługa błędów”.

Okrajanie

Zwróć uwagę, że funkcje wirtualne działają tylko w przypadku wskaźników i referencji. Przekazanie obiektu przez wartość uniemożliwia wywołanie funkcji wirtualnych. Problem ten ilustruje listing 12.10.

Listing 12.10. Okrajanie (przyci[Author ID1: at Wed Oct 31 12:27:00 2001 ]ęcie[Author ID1: at Wed Oct 31 12:45:00 2001 ]) [Author ID1: at Wed Oct 31 12:27:00 2001 ]danych podczas przekazywania przez wartość

0: //Listing 12.10 Okrajanie danych podczas przekazywania przez wartość

1:

2: #include <iostream>

3:

4: class Mammal

5: {

6: public:

7: Mammal():itsAge(1) { }

8: virtual ~Mammal() { }

9: virtual void Speak() const { std::cout << "Ssak mowi!\n"; }

10: protected:

11: int itsAge;

12: };

13:

14: class Dog : public Mammal

15: {

16: public:

17: void Speak()const { std::cout << "Hau!\n"; }

18: };

19:

20: class Cat : public Mammal

21: {

22: public:

23: void Speak()const { std::cout << "Miau!\n"; }

24: };

25:

26: void ValueFunction (Mammal);

27: void PtrFunction (Mammal*);

28: void RefFunction (Mammal&);

29: int main()

30: {

31: Mammal* ptr=0;

32: int choice;

33: while (1)

34: {

35: bool fQuit = false;

36: std::cout << "(1)pies (2)kot (0)Wyjscie: ";

37: std::cin >> choice;

38: switch (choice)

39: {

40: case 0: fQuit = true;

41: break;

42: case 1: ptr = new Dog;

43: break;

44: case 2: ptr = new Cat;

45: break;

46: default: ptr = new Mammal;

47: break;

48: }

49: if (fQuit)

50: break;

51: PtrFunction(ptr);

52: RefFunction(*ptr);

53: ValueFunction(*ptr);

54: }

55: return 0;

56: }

57:

58: void ValueFunction (Mammal MammalValue)

59: {

60: MammalValue.Speak();

61: }

62:

63: void PtrFunction (Mammal * pMammal)

64: {

65: pMammal->Speak();

66: }

67:

68: void RefFunction (Mammal & rMammal)

69: {

70: rMammal.Speak();

71: }

Wynik

(1)pies (2)kot (0)Wyjscie: 1

Hau!

Hau!

Ssak mowi!

(1)pies (2)kot (0)Wyjscie: 2

Miau!

Miau!

Ssak mowi!

(1)pies (2)kot (0)Wyjscie: 0

Analiza

W liniach od 4. do 24. są deklarowane okrojone klasy Mammal, Dog oraz Cat. Deklarowane są trzy funkcje — PtrFunction(), RefFunction() oraz ValueFunction(). Przyjmują one, odpowiednio, wskaźnik do obiektu klasy Mammal, referencję do takiego obiektu oraz sam obiekt Mammal. Wszystkie trzy funkcje robią to samo: wywołują metodę Speak().

Użytkownik jest proszony o wybranie albo [Author ID1: at Wed Oct 31 12:28:00 2001 ]klasy [Author ID1: at Wed Oct 31 12:28:00 2001 ]Dog[Author ID1: at Wed Oct 31 12:28:00 2001 ]Pies[Author ID1: at Wed Oct 31 12:28:00 2001 ] albo [Author ID1: at Wed Oct 31 12:28:00 2001 ]lub [Author ID1: at Wed Oct 31 12:28:00 2001 ]Cat[Author ID1: at Wed Oct 31 12:28:00 2001 ]Kot[Author ID1: at Wed Oct 31 12:28:00 2001 ] [Author ID1: at Wed Oct 31 12:45:00 2001 ] [Author ID1: at Wed Oct 31 12:45:00 2001 ]i, w zależności od tego wyboru, w liniach od 42. do 45. tworzony jest wskaźnik do odpowiedniego typu [Author ID1: at Wed Oct 31 12:28:00 2001 ]([Author ID1: at Wed Oct 31 12:29:00 2001 ]Dog[Author ID1: at Wed Oct 31 12:28:00 2001 ] lub [Author ID1: at Wed Oct 31 12:28:00 2001 ]Cat[Author ID1: at Wed Oct 31 12:28:00 2001 ])[Author ID1: at Wed Oct 31 12:28:00 2001 ].

W pierwszej linii wydruku [Author ID1: at Wed Oct 31 12:29:00 2001 ]niku[Author ID1: at Wed Oct 31 12:29:00 2001 ] użytkownik wybrał klasę Dog. Obiekt klasy Dog został utworzony na stercie (w linii 42.). Następnie nowo utworzony obiekt jest przekazywany wspomnianym wyżej trzem funkcjom poprzez wskaźnik, referencję i wartość.

Wskaźnik i referencje wywołują funkcje wirtualne, zatem zostaje wywołana funkcja składowa Dog::Speak(). Obrazują to następne dwie linie wydruk[Author ID1: at Wed Oct 31 12:30:00 2001 ]u[Author ID1: at Wed Oct 31 12:46:00 2001 ] [Author ID1: at Wed Oct 31 12:30:00 2001 ](ników[Author ID1: at Wed Oct 31 12:30:00 2001 ]po wyborze dokonanym przez użytkownika).

Wyłuskany wskaźnik jest jednak przekazywany poprzez wartość. Funkcja oczekuje obiektu klasy Mammal, więc kompilator „okraja” obiekt klasy Dog, pozostawiając jedynie część stanowiącą obiekt klasy Mammal. W tym momencie wywoływana jest więc metoda Speak() klasy Mammal, co odzwierciedla trzecia linia wydruku[Author ID1: at Wed Oct 31 12:30:00 2001 ] (niku[Author ID1: at Wed Oct 31 12:30:00 2001 ]po wyborze dokonanym przez użytkownika).

Ten eksperyment zostaje następnie powtórzony dla klasy Cat; uzyskaliśmy podobne rezultaty.

Destruktory wirtualne

Dozwolone, i dość często stosowane, jest przekazywanie wskaźnika do wyprowadzonego obiektu tam, gdzie oczekiwany jest wskaźnik do klasy bazowej. Co się dzieje, gdy taki wskaźnik do wyprowadzonej klasy zostaje usunięty? Jeśli destruktor jest wirtualny, a taki powinien być, dzieją[Author ID1: at Wed Oct 31 12:31:00 2001 ] następuje to[Author ID1: at Wed Oct 31 12:31:00 2001 ], co powinno [Author ID1: at Wed Oct 31 12:31:00 2001 ]się właściwe rzeczy[Author ID1: at Wed Oct 31 12:31:00 2001 ]: zostaje wywoływany destruktor klasy wyprowadzonej. Ponieważ destruktor klasy wyprowadzonej automatycznie wywołuje destruktor klasy bazowej, cały obiekt zostanie zniszczony.

Obowiązuje tu następująca zasada: jeśli jakiekolwiek funkcje w klasie są wirtualne, destruktor także powinien być wirtualny.

Wirtualne konstruktory kopiujące[Author ID1: at Wed Oct 31 12:31:00 2001 ]i[Author ID1: at Wed Oct 31 12:31:00 2001 ]

Konstruktory nie mogą być wirtualne[Author ID1: at Wed Oct 31 12:32:00 2001 ]y[Author ID1: at Wed Oct 31 12:32:00 2001 ], więc nie istnieje coś takiego, jak wirtualny konstruktor kopiując[Author ID1: at Wed Oct 31 12:32:00 2001 ]yi[Author ID1: at Wed Oct 31 12:32:00 2001 ]. Zdarza się jednak, że musimy przekazać wskaźnik do obiektu bazowego i otrzymać kopię właściwie utworzonego obiektu klasy wyprowadzonej. Powszechnie stosowanym rozwiązaniem tego problemu jest stworzenie metody o nazwie Clone() (klonuj) w klasie bazowej i uczynienie jej metodą wirtualną. Metoda Clone() tworzy i zwraca nową kopię obiektu bieżącej klasy.

Ponieważ metoda Clone() zostaje przesłonięta w każdej z klas pochodnych, tworzona jest kopia klasy wyprowadzonej. Ilustruje to listing 12.11.

Listing 12.11. Wirtualny konstruktor kopiujący[Author ID1: at Wed Oct 31 12:32:00 2001 ]i[Author ID1: at Wed Oct 31 12:32:00 2001 ]

0: //Listing 12.11 Wirtualny konstruktor kopiujący[Author ID1: at Wed Oct 31 12:32:00 2001 ]i[Author ID1: at Wed Oct 31 12:32:00 2001 ]

1:

2: #include <iostream>

3: using namespace std;

4:

5: class Mammal

6: {

7: public:

8: Mammal():itsAge(1) { cout << "Konstruktor klasy Mammal...\n"; }

9: virtual ~Mammal() { cout << "Destruktor klasy Mammal...\n"; }

10: Mammal (const Mammal & rhs);

11: virtual void Speak() const { cout << "Ssak mowi!\n"; }

12: virtual Mammal* Clone() { return new Mammal(*this); }

13: int GetAge()const { return itsAge; }

14: protected:

15: int itsAge;

16: };

17:

18: Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge())

19: {

20: cout << "Konstruktor kopiuj[Author ID1: at Wed Oct 31 12:32:00 2001 ]a[Author ID1: at Wed Oct 31 12:35:00 2001 ]cy[Author ID1: at Wed Oct 31 12:32:00 2001 ]i[Author ID1: at Wed Oct 31 12:32:00 2001 ] klasy Mammal...\n";

21: }

22:

23: class Dog : public Mammal

24: {

25: public:

26: Dog() { cout << "Konstruktor klasy Dog...\n"; }

27: virtual ~Dog() { cout << "Destruktor klasy Dog...\n"; }

28: Dog (const Dog & rhs);

29: void Speak()const { cout << "Hau!\n"; }

30: virtual Mammal* Clone() { return new Dog(*this); }

31: };

32:

33: Dog::Dog(const Dog & rhs):

34: Mammal(rhs)

35: {

36: cout << "Konstruktor kopiuj[Author ID1: at Wed Oct 31 12:33:00 2001 ]a[Author ID1: at Wed Oct 31 12:35:00 2001 ]cy[Author ID1: at Wed Oct 31 12:33:00 2001 ]i[Author ID1: at Wed Oct 31 12:33:00 2001 ] klasy Dog...\n";

37: }

38:

39: class Cat : public Mammal

40: {

41: public:

42: Cat() { cout << "Konstruktor klasy Cat...\n"; }

43: ~Cat() { cout << "Destruktor klasy Cat...\n"; }

44: Cat (const Cat &);

45: void Speak()const { cout << "Miau!\n"; }

46: virtual Mammal* Clone() { return new Cat(*this); }

47: };

48:

49: Cat::Cat(const Cat & rhs):

50: Mammal(rhs)

51: {

52: cout << "Konstruktor kopiuj[Author ID1: at Wed Oct 31 12:33:00 2001 ]a[Author ID1: at Wed Oct 31 12:36:00 2001 ]cy[Author ID1: at Wed Oct 31 12:33:00 2001 ]i[Author ID1: at Wed Oct 31 12:33:00 2001 ] klasy Cat...\n";

53: }

54:

55: enum ANIMALS { MAMMAL, DOG, CAT};

56: const int NumAnimalTypes = 3;

57: int main()

58: {

59: Mammal *theArray[NumAnimalTypes];

60: Mammal* ptr;

61: int choice, i;

62: for ( i = 0; i<NumAnimalTypes; i++)

63: {

64: cout << "(1)dog (2)cat (3)Mammal: ";

65: cin >> choice;

66: switch (choice)

67: {

68: case DOG: ptr = new Dog;

69: break;

70: case CAT: ptr = new Cat;

71: break;

72: default: ptr = new Mammal;

73: break;

74: }

75: theArray[i] = ptr;

76: }

77: Mammal *OtherArray[NumAnimalTypes];

78: for (i=0;i<NumAnimalTypes;i++)

79: {

80: theArray[i]->Speak();

81: OtherArray[i] = theArray[i]->Clone();

82: }

83: for (i=0;i<NumAnimalTypes;i++)

84: OtherArray[i]->Speak();

85: return 0;

86: }

Wynik

1: (1)dog (2)cat (3)Mammal: 1

2: Konstruktor klasy Mammal...

3: Konstruktor klasy Dog...

4: (1)dog (2)cat (3)Mammal: 2

5: Konstruktor klasy Mammal...

6: Konstruktor klasy Cat...

7: (1)dog (2)cat (3)Mammal: 3

8: Konstruktor klasy Mammal...

9: Hau!

10: Konstruktor kopiujacy[Author ID1: at Wed Oct 31 12:36:00 2001 ]i[Author ID1: at Wed Oct 31 12:36:00 2001 ] klasy Mammal...

11: Konstruktor kopiujacy[Author ID1: at Wed Oct 31 12:36:00 2001 ]i[Author ID1: at Wed Oct 31 12:36:00 2001 ] klasy Dog...

12: Miau!

13: Konstruktor kopiujacy[Author ID1: at Wed Oct 31 12:36:00 2001 ]i[Author ID1: at Wed Oct 31 12:36:00 2001 ] klasy Mammal...

14: Konstruktor kopiujacy[Author ID1: at Wed Oct 31 12:36:00 2001 ]i[Author ID1: at Wed Oct 31 12:36:00 2001 ] klasy Cat...

15: Ssak mowi!

16: Konstruktor kopiujacy[Author ID1: at Wed Oct 31 12:36:00 2001 ]i[Author ID1: at Wed Oct 31 12:36:00 2001 ] klasy Mammal...

17: Hau!

18: Miau!

19: Ssak mowi!

Analiza

Listing 12.11 jest bardzo podobny do dwóch poprzednich listingów, z wyjątkiem tego, iż tym razem do klasy Mammal została dodana nowa wirtualna metoda, o nazwie Clone(). Ta metoda zwraca wskaźnik do nowego obiektu klasy Mammal poprzez wywołanie konstruktora kopiującego[Author ID1: at Wed Oct 31 12:37:00 2001 ]i[Author ID1: at Wed Oct 31 12:37:00 2001 ], przekazując samą siebie (*this) jako stałą (const) referencję.

Metoda Clone() została przeciążona zarówno w klasie Dog, jak i w klasie Cat, w których inicjalizuje dane tych klas i przekazuje kopie samych siebie do swoich własnych konstruktorów kopiujących[Author ID1: at Wed Oct 31 12:37:00 2001 ]i[Author ID1: at Wed Oct 31 12:37:00 2001 ]. Ponieważ metoda Clone() jest wirtualna, w efekcie otrzymujemy wirtualny konstruktor kopiujący[Author ID1: at Wed Oct 31 12:37:00 2001 ]i[Author ID1: at Wed Oct 31 12:37:00 2001 ], co pokazano w linii 81.

Użytkownik jest proszony o wybranie klasy Dog, Cat lub Mammal, a w liniach od 62. do 74. tworzony jest odpowiedni obiekt. Wskaźnik dla każdego z wyborów jest umieszczany w tablicy w linii 75.

Gdy program „przechodzi” przez kolejne elementy tablicy, wywołuje metodę Speak() i Clone() każdego ze wskazywanych obiektów (w liniach 80. i 81.). Rezultatem wywołania metody Clone() jest wskaźnik do kopii obiektu, który w linii 81 jest przechowywany w drugiej tablicy.

W pierwszej linii wydruku [Author ID1: at Wed Oct 31 12:38:00 2001 ]niku[Author ID1: at Wed Oct 31 12:38:00 2001 ] użytkownik wybiera pierwszą opcję, tworząc klasę Dog. Wywoływane są konstruktory klasy Mammal oraz klasy Dog. Czynność tę powtarza się dla klas Cat oraz Mammal w liniach wyników od 4. do 8ników[Author ID1: at Wed Oct 31 12:38:00 2001 ].

Linia 9. wyników reprezentuje wywołanie metody Speak() pierwszego obiektu, należącego do klasy Dog. Wywoływana jest wirtualna metoda Speak(), zatem zostaje wywołana metoda właściwej klasy. Następnie wywoływana jest metoda Clone(), a ponieważ ona także jest wirtualna, w efekcie zostaje wywołana metoda Clone() klasy Dog. Powoduje to wywołanie konstruktora klasy Mammal oraz konstruktora kopiującego[Author ID1: at Wed Oct 31 12:38:00 2001 ]i[Author ID1: at Wed Oct 31 12:38:00 2001 ] klasy Dog.

Te same czynności powtarza się dla klasy Cat w liniach wyników od 12. do 14., a następnie dla klasy Mammal w liniach 15. i 16. Na zakończenie program „przechodzi” przez drugą tablicę, w której dla każdego z nowych obiektów zostaje wywołana metoda Speak().

Koszt metod wirtualnych

Ponieważ obiekty zawierające metody wirtualne muszą przechowywać tablice funkcji wirtualnych (v-table), z posiadaniem metod wirtualnych wiąże się pewne obciążenie. Jeśli posiadasz bardzo małą klasę, z której nie zamierzasz wyprowadzać innych klas, być może nie ma powodu umieszczania w niej jakichkolwiek metod wirtualnych.

Gdy zadeklarujesz którąś z metod jako wirtualną, poniesiesz już większość kosztów posiadania tablicy funkcji wirtualnych (choć każda z kolejnych funkcji wirtualnych także powoduje pewne niewielkie obciążenie pamięci). Powinieneś także posiadać wirtualny destruktor (zakładając także, że wszystkie inne metody również najprawdopodobniej będą wirtualne). Dokładnie przyjrzyj się każdej z niewirtualnych metod i upewnij się że, czy wiesz, dlaczego nie są one wirtualne.

TAK

NIE

Gdy spodziewasz się, że będziesz wyprowadzał nowe klasy z klasy, którą właśnie tworzysz, użyj metod wirtualnych.

Jeśli którakolwiek z metod jest wirtualna, użyj także wirtualnego destruktora.

Nie oznaczaj konstruktora jako funkcji wirtualnej.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 F:\korekta\r12-06.doc[Author ID2: at Wed Nov 21 09:15:00 2001 ]C:\Moje dokumenty\jr\doc\Korekt_rzeczo\2\Kopia r12-05.doc[Author ID2: at Wed Nov 21 09:15:00 2001 ]



Wyszukiwarka

Podobne podstrony:
1233
1233
1233
1233
Do końca kalendarza Majów pozostało jeszcze 1233 dni (1)
2004.118.1233 - Rozp. w sprawie studium u. i k. z. p. g, Budownictwo 2, Budownictwo, Urbanistyka, Go
1233
1233
1233
1233
1233
K Guzikowski Rywalizacja i współpraca Polityka Barnima I (1233–1278) i Bogusława IV wobec Piastów (
HAE JW 1233 Freedom

więcej podobnych podstron