Rozdział 11
Dziedziczenie
Klasy bazowe i klasy pochodne .1 Dzcedziczecaie polega na ustanowieniu związku
pomiędzy dwoma klasami, z których jedna jest klasa bazowd, a druga klasa pochodrcc~, czyli klasą dziedziczącą. Po zdefiniowaniu takiego związku definicja klasy pochodnej jest rozszerzana o wskazane składowe pochodzące z klasy bazowej (zarówno dane składowe, jak i funkcje składowe). Obiekty klasy pochodnej zawierają więc dane specyficzne dla klasy pochodnej (określone w definicji klasy pochodnej) oraz dane odziedziczone z klasy bazowej (określone w definicji klasy bazowej). Podobnie zestaw funkcji składowych, które można aktywizować dla obiektów klasy pochodnej, składa się z funkcji zdefiniowanych w klasie pochodnej i z funkcji odziedziczonych z klasy bazowej. Dziedziczenie zadaje się, umieszczając w definicji klasy pochodnej po jej identyfikatorze identyfikator klasy bazowej, poprzedzony słowem kluczowym wskazującym tryb dziedziczenia:
elass id klasy~ochodrtej : tryb d<'.iedz.iczercia id klasy bazowej { cialo klasy~ochodnej }
Tryb dziedziczenia wskazywany jest za pomocą słów kluczowych public, protected lub private. Omówienie trybów dziedziczenia zostanie przedstawione w dalszej części tego rozdziału.
204
Rozdziaf Il. Dziedziczenie
class Publikacja // klasa bazowa i
public: char *m szTytuł ; float m flCena ;
void ZmianaCeny ( float flNowaCena )
m flCena = flNowaCena ;
i
void WyświetlTytuł ( )
cout « m szTytuł ;
i i~
class Ksiażka : public Publikacja public:
char *m szAutor ;
void WyświetlAutora ( )
cout « m szAutor ;
i~
class Czasopismo : public Publikacja t
public: char `m szCykl ;
// klasa pochodna
// klasa pochodna
r
Il.l. Klaso bazowe i klasy pochodne
void WyświetlCykl ( )
cout « m szCykl ;
Klasa bazowa Publikacja zawiera podstawowe składowe m szTytuł i m flCena opisujące dowolną publikację oraz funkcje składowe ZmianaCeny i WyświetlTytuł. Klasa pochodna Ksiażka wprowadza dodatkową składową określającą autora i funkcję umożliwiającą wyświetlanie na ekranie danych o autorze. Obiekty klasy Książka będą więc zawierały składowe: m szTytuł, m flCena, m szAutor; można dla nich aktywizować funkcje składowe ZmianaCeny, WyświetlTytuł i WyświetlAutora. Klasa pochodna Czasopismo wprowadza natomiast składową m szCykl, określającą typ czasopisma (dziennik, tygodnik, ...) i funkcję składową WyświetlCykl. Obiekty tej klasy zawierają składowe m szTytuł, m flCena, m szCykl; można dla nich aktywizować funkcje składowe ZmianaCeny, WyświetlTytuł i WyświetlCykl. Budowę obiektów tych trzech klas przedstawia rysunek 11.1. Klasa bazowa jest - jak widać - generalizacją (uogólnieniem) klas pochodnych, klasy pochodne są specjalizacjami klasy bazowej. W programie najczęściej tworzy się jedynie obiekty klas pochodnych - klasa bazowa służy jako definicja składowych wspólnych dla wszystkich klas pochodnych.
Publikacja Książka Czasopismo
pszTytuł ysz I'ytul m szTytuł m f7Cena m_flCena m flCena
m szAutor pszCykl
Rys. 11.1. Budowa obiektów klasy bazowej i klas pochodnych
206 Rozdział l1. Dziedziczenie
11.2
Hierarchia klas
Dziedziczenie pojedyncze i wielokrotne 1 1 .G. 1 Klasa pochodna może dziedziczyć z więcej niż jednej
klasy bazowej. Związek klasy pochodnej z jedną klasą bazową nazywamy dziedziczenie~rz pojedynczym. Gdy natomiast klasa pochodna jest związana z wieloma klasami bazowymi, występuje dziedziczenie Yvielokrottae. Przedstawiona w poprzednim punkcie klasa bazowa Publikacja oraz klasy pochodne Książka i Czasopismo są przykładem dziedziczenia pojedynczego. Przy dziedziczeniu wielokrotnym w obiekcie klasy pochodnej występują, poza składowymi specyficznymi tej klasy, składowe odziedziczone z wszystkich klas bazowych.
class Samolot // klasa bazowa
public: char *m szRodzajNapędu ; int m nZasięg ;
class Transport // klasa bazowa public:
int m nLiczbaPasażerów ; int m nŁadowność ;
class SamolotPasażerski : public Samolot, public Transport
// klasa pochodna
char 'm szLiniaLotnicza ;
J1.2. Hierarchia klas
207
Obiekty klasy SamolotPasażerski będą miały następujące składowe: m szRodzajNapędu - odziedziczone z klasy bazowej Samolot,
m nZasięg - odziedziczone z klasy bazowej Samolot,
m nLiczbaPasażerów -odziedziczone z klasy bazowej Transport, m nŁadowność - odziedziczone z klasy bazowej Transport,
m szLiniaLotnicza - składowa specyficzna klasy SamolotPasażerski.
Dla obiektów tych można aktywizować funkcje składowe odziedziczone z wszystkich klas bazowych i funkcje składowe specyficzne, zdefiniowane w klasie pochodnej.
Drzewa i grafy dziedziczenia 1 1 .2.2 glasa bazowa pewnej klasy pochodnej może być również
klasą pochodną swojej klasy bazowej. Właściwość ta pozwala na tworzenie hierarchii klas. Jeżeli w hierarchii występuje jedynie dziedziczenie pojedyncze, to hierarchię można przedstawić za pomocą drzewa (rys. 11.2.).
Rys. 11.2. Drzewo dziedziczenia
W drzewie dziedziczenia można dla każdej klasy wskazać liniową hierarchię, począwszy od głównej klasy bazowej (rys. 11.3.). Natomiast gdy w hierarchii występuje dziedziczenie wielokrotne, to można ją przedstawić za pomocą grafa (rys. 1 I .4.).
208
Ro~dziat Il. D~iedzie=enie
Rys. 11.3. Hierarchia klasy Węglowe
Rys. 11.4. Graf dziedziczenia
Powszechne i prywatne 11 . ~ klasy bazowe
Definiując klasę bazową, można wskazać składowe, które będą dziedziczone przez klasy pochodne.
Są to wszystkie składowe występujące w sekcji public i w sekcji protected.
11.3. Powszechne i pz.lwatne klasy bazowe
Jak to już było powiedziane, sekcje występujące w definicji klasy określają następujące prawa dostępu do składowych:
• sekcja public zawiera składowe powszechne, dziedziczone przez klasy pochodne, dostępne w całym programie,
• sekcja private zawiera składowe prywatne, nie podlegające dziedziczeniu, dostępne jedynie dla funkcji składowych klasy i dla funkcji zaprzyjaźnionych, • sekcja protected zawiera składowe chronione, dziedziczone przez klasy
pochodne, dostępne dla funkcji składowych klasy bazowej i funkcji zaprzyjaźnionych z tą klasą.
class Rama // klasa bazowa
private: int m_nLiczbaSztuk ; protected:
int m nSzerokość ; int m nWysokość ; public:
char *m szDataPobrania ;
i~
class Akwarela : public Rama // klasa pochodna
public: char *m szAutor ;
char *m szTytuł ;
i~
Obiekty klasy bazowej Rama zawierają trzy składowe określające wysokość i szerokość ramy oraz liczbę sztuk ram o danym rozmiarze. Gdy rama zostanie użyta do oprawy obrazu, tworzony jest obiekt klasy Akwarela, który dziedziczy składowe opisujące rozmiar i datę. W obiekcie tej klasy występują następujące składowe: m szAutor, m szTytuł, m_nSzerokość, m nWysokość, m szpata Pobrania. Klasa pochodna dziedziczy składowe sekcji public i sekcji protected klasy bazowej. Składowe te mogą być dołączane do różnych sekcji klasy pochodnej, zależnie od trybu dziedziczenia (wspomnianego już w punkcie 1 l.l)
210 RozcL,iał ll. DziecLiczenie
• przy dziedziczeniu powszechnym, zadawanym za pomocą słowa kluczowego public
- składowe dziedziczone z sekcji public klasy bazowej są dołączane do sekcji public klasy pochodnej,
- składowe dziedziczone z sekcji protected klasy bazowej są dołączane do i sekcji protected klasy pochodnej,
• przy dziedziczeniu chronionym, zadawanym za pomocą słowa kluczowego protected, zarówno składowe dziedziczone z sekcji public jak i z sekcji protected są dołączane do sekcji protected klasy pochodnej,
• przy dziedziczeniu prywatnym, zadawanym za pomocą słowa kluczowego private, zarówno składowe dziedziczone z sekcji public jak i z sekcji protected klasy bazowej sądołączane do sekcji private klasy pochodnej.
Przy dziedziczeniu powszechnym składowe odziedziczone z klasy bazowej nie zmieniają więc swego charakteru, przy dziedziczeniu chronionym wszystkie .r;,
odziedziczone składowe stają się składowymi chronionymi, a przy dziedziczeniu prywatnym składowe odziedziczone stają się składowymi prywatnymi klasy pochodnej.
`~~~~' class KIasaPierwsza
private: int m_nAtrybutPierwszy ; protected:
int m nAtrybutDrugi ; public:
int m_nAtrybutTrzeci ; ?;
class KIasaDruga : public KIasaPierwsza
/* składowa m_nAtrybutDrugi zostanie dołączona do sekcji protected: składowa m nAtrybutTrzeci zostanie dołączona do sekcji public: (sekcje public: i protected: mogąca klasie KIasaDruga bezpośrednio nie występować) */
11.3. Powszechne i pywahte klasy bazowe 211
class KIasaTrzecia : private KIasaPierwsza i
/* składowe m nAtrybutDrugi i m nAtrybutTrzeci zostaną dołączone do sekcji private: (sekcja private: może w klasie KIasaTrzecia bezpośrednio nie występować) */
i;
class KIasaCzwarta : public KIasaDruga i
/* dziedziczone są składowe m_nAtrybutDrugi i m nAtrybutTrzeci */ };
class KIasaPiąta : public KIasaTrzecia (
/* z klasy bazowej nie są dziedziczone żadne składowe */
i;
Tak więc z poziomu programu do składowej m_nAtrybutDrugi można uzyskać dostęp poprzez obiekty klas KIasaPierwsza, KIasaDruga i KIasaCzwarta, a nie można poprzez obiekty klas KIasaTrzecia i KIasaPiąta. W klasie pochodnej można zmieniać charakter dziedziczonych składowych, wskazując bezpośrednio za pomocą operatora zakresu ( :: ), do której sekcj i składowa ma zostać dołączona.
class KIasaSzósta i
public: int m nAtrybutCzwarty ; int m nAtrybutPiąty ;
i;
class KIasaSiódma : protected KIasaSzósta public:
KIasaSzósta : : m_nAtrybutCzwarty ;
212 Rozd~iai ll. Dzied=żczenie i
1 /" składowa m_nAtrybutCzwarty odziedziczona z klasy bazowej KIasaSzósta ' zostanie dołączona do sekcji public zamiast do sekcji protected */
/* składowa m nAtrybutPiąty zostanie dołączona do sekcji protected '/ };
Wirtualne klasy bazowe 11 ~ -T W grafie dziedziczenia może wystąpić sytuacja,
w której klasa pochodna dziedziczyłaby wielokrotnie z tej samej klasy bazowej. Aby zapobiec wielokrotnemu powielaniu tych samych składowych, dziedziczonych z klas bazowych, należy w klasie pochodnej zadeklarować wirtualną klasę bazową. Dokonuje się tego przez umieszczenie słowa kluczowego virtual przed określeniem trybu ;!'I dziedziczenia klasy bazowej.
class Zwierzęta u~'' {
public: char "m szNazwaGatunkowa ; };
class Ssaki : virtual public Zwierzęta class Morskie : virtual public Zwierzęta
class Orka : public Ssaki, public Morskie
Dzięki wprowadzeniu wirtualnej klasy bazowej Zwierzęta, obiekty klasy Orka będą miały tylko jedną składową o identyfikatorze m szNazwaGatunkowa.
11.5. Obiekt~~ klas pochodyych 213
11.5
Obiekty klas pochodnych
Tworzenie
Podczas tworzenia obiektu klasy pochodnej wywoływane są konstruktory klas bazowych, począwszy od klasy najstarszej w hierarchii.
class Statki i
protected: float m flTonaż ; Statki ( )
i
m flTonaż = 1000 ; i
i.
class Handlowe : public Statki i
protected: float m flŁadowność ; Handlowe ( )
f m flŁadowność = 700 ; ?;
//
214 Rozdział Il. Driedziczenie
class Kontenerowce : public Handlowe i
public: int m_nLiczbaKontenerów ;
Kontenerowce ( ) {
m_nLiczbaKontenerów = 70 ;
} i
};
;j~ Kontenerowce KontenerowiecStandardowy ; ,~i
W przykładzie tym jest tworzony obiekt KontenerowiecStandardowy klasy Kontenerowce. Konstruktory inicjujące ten obiekt zostaną wywołane w kolejności: Statki, Handlowe, Kontenerowce i nadadzą składowym standardowe wartości:
m flTonaż == 1000 m flŁadowność == 700
m_nLiczbaKontenerów == 70
Jeżeli konstruktory klas bazowych wymagają argumentów, to należy wywołania tych konstruktorów umieścić na liście powiązań składowych i argumentów konstruktora klasy pochodnej (lista ta była już omawiana w rozdziale 9). Składowe, których wartości początkowych nie ustalają konstruktory klas bazowych, mogą być inicjowane przez konstruktor klasy pochodnej. W konstruktorze tym można również dokonać zmiany wartości składowych, które zostały uprzednio zainicjowane przez konstruktory klas bazowych (konstruktor klasy pochodnej ma więc ostatnie słowo w procesie inicjowania obiektu klasy pochodnej).
class Statki {
protected: float m flTonaż ; char *m szArmator ;
Statki ( float flTonaż ) : m flTonaż ( flTonaż ) { } } ;
11.5. Obiekt~~ klas poc/zodnych
class Handlowe : public Statki {
protected: float m flŁadowność ;
Handlowe ( float flTonaż, float flŁadowność)
Statki ( flTonaż ), m flŁadowność ( flŁadowność ) { } i;
class Kontenerowce : public Handlowe {
public; int m nLiczbaKontenerów ;
Kontenerowce ( float flTonaż, float flŁadowność,
int nLiczbaKontenerów, char* szArmator) :
Handlowe ( flTonaż, flŁadowność ), m_nLiczbaKontenerów ( flLiczbaKontenerów )
armator = new char [ strlen ( szArmator ) + 1 ] ;
strcpy ( m szArmator, szArmator ) ;
i }>
Kontenerowce KontenerowiecPopularny ( 2500, 1750, 100, "Żegluga Bałtycka" ) ;
W tym przypadku podczas deklarowania obiektu klasy Kontenerowce należy podać argumenty, które będą wykorzystane przy wywołaniach kolejnych konstruktorów.
Przy usuwaniu obiektu klasy pochodnej destruktory są wywoływane w kolejności odwrotnej do kolejności wywołania konstruktorów.
2,16 Ro=dniał Il. Daiedaiczenłe
Dostęp do składowych 11 5 2
Ogólne zasady dostępu do składowych obiektu klasy pochodnej są takie same jak dla obiektów klas nie
dziedziczących składowych. Wyjaśnienia wymaga jednak kilka zagadnień szczegółowych.
Dziedziczenie składowych o takich samych identyfikatorach
Gdy w kilku klasach bazowych występują składowe podlegające dziedziczeniu o takich samych identyfikatorach, to dziedzicząca wielokrotnie klasa pochodna będzie zawierała kilka składowych o takim samym identyfikatorze. W odwołaniach do tych składowych należy identyfikator składowej poprzedzać identyfikatorem klasy bazowej z operatorem zakresu ( :: ). Ta wkaściwość dziedziczenia uniemożliwia wprowadzanie dziedziczonych funkcji składowych o przeciążonym identyfikatorze (przeciążanie identyfikatora jest możliwe jedynie dla funkcji składowych tej samej klasy).
c~ass Ptaki
public: char ~m szNnazwa ;
char m cNielotne ;
void Drukuj ( chat " szTekst = " " )
cout « szTekst « m szNazwa ;
} i~
class ZwierzętaDomowe {
public: chat m szNazwa ;
~i
void Drukuj ( )
{ cout « "Rodzaj zwierząt domowych : " « m szNazwa ; } } ;
l L5. Ohiekh~ klas pocho~lm~c/z
class Indyk : public Ptaki, public ZwierzętaDomowe public:
int m_nNumer ; float m flWaga ; );
Indyk indyk ;
indyk . m szNazwa = "xxx " ; // błąd, niejednoznaczność indyk . Ptaki::m szNazwa = "Meleagris gallopavo" ;
indyk . ZwierzętaDomowe::m szNazwa = "Drób" ; indyk . m cNielotne = 1 ;
indyk . m nNumer = 157 ; indyk . m flWaga = 12.4 ;
indyk . Drukuj ( ) ; // błąd, niejednoznaczność indyk . Ptaki::Drukuj ( "Gatunek ptaków : " ) ;
indyk . ZwierzętaDomowe::Drukuj ( ) ;
Wskazywania klasy bazowej, z której odziedziczona została składowa, można oczywiście uniknąć, nadając wszystkim składowym hierarchii klas unikatowe identyfikatory.
Dziedziczenie a funkcje zaprzyjaźnione
W definicji funkcji zaprzyjaźnionej klasy można odwoływać się do wszystkich składowych tej klasy (powszechnych, chronionych i prywatnych). Związek zaprzyjaźnienia nie podlega jednak dziedziczeniu. Jeżeli klasa posiadająca funkcję zaprzyjaźnioną jest klasą bazową pewnej klasy pochodnej, to w funkcji zaprzyjaźnionej nie można odwoływać się do składowych prywatnych i chronionych tej klasy pochodnej. Wyjątkiem są składowe chronione klasy pochodnej, które zostały odziedziczone. z klasy bazowej - do takich składowych ma dostęp funkcja zaprzyjaźniona klasy bazowej. Natomiast funkcja zaprzyjaźniona klasy pochodnej nie może odwoływać się do składowych prywatnych i chronionych klasy bazowej.
218
Ro=dual lI. D=iecLiezenie
class Deser f
friend void Rozliczenie ( ) ;
private:
char *m szMarża ;
protected:
char *m szPrzepis ;
public:
char *m szNazwa ;
float m flCena ;
...
i;
class Lody : public Deser f
friend void Przygotowanie ( ) ;
private:
char m cBakalie ;
protected:
char "m szKalkulacja ;
public:
char *m szSmak ;
}; void Rozliczenie ( ) { Deser deser;
Lody lody ;
deser . m szMarża ; deser . m szPrzepis ; deser . m szNazwa ;
// funkcja zaprzyjaźniona klasy Deser
// poprawnie // poprawnie // poprawnie
I L5. Obiekty klas pochodnych 219
deser , m flCena ;
lody . m cBakalie ;
1 _
j lody . m szKalkulacja ;
i
lody . m szPrzepis ;
lody . m szSmak ;
lody . m szNazwa ;
lody . m flCena ;
void Przygotowanie ( ) { Deser deser ;
Lody lody ;
i deser . m szMarża ; deser . m szPrzepis ; i
deser , m szNazwa ; deser . m flCena ; lody . m cBakalie ;
r lody . m szKalkulacja ; lody . m szPrzepis ;
i lody . m szSmak ; j _
lody . m szNazwa ; lody . m flCena ;
// poprawnie // błąd
l/ błąd
// poprawnie ( ! ) // poprawnie
// poprawnie // poprawnie
// funkcja zaprzyjaźniona klasy Lody
/l błąd // błąd
// poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie
i
W funkcji Rozliczenie, będącej funkcją zaprzyjaźnioną klasy Deser, można odwołać się do składowej chronionej m szPrzepis obiektu klasy pochodnej Lody. Składowa ta została bowiem odziedziczona z klasy bazowej, z którą zaprzyjaźniona jest funkcja Rozliczenie. Podobne odwołanie do składowej chronionej m szKalkulacja obiektu klasy pochodnej jest niepoprawne (składowa ta nie została odziedziczona z klasy bazowej).
' 220 Ro~cG:ic~t II. D~ied~iczenie I',
Zakres identyfikatorów Po napotkaniu w klasie pochodnej identyfikatora zmiennej lub funkcji kompilator poszukuje definicji tej zmiennej czy funkcji, sprawdzając kolejno:
• czy jest to identyfikator składowej klasy pochodnej,
• czy jest to identyfikator składowej klasy bazowej występującej w hierarchii dziedziczenia klasy pochodnej,
• czy jest to identyfikator zadeklarowany na poziomie pliku.
Konwersja 11.5.3
,j', Konwersja obiektów klas bazowych i pochodnych
Przy założeniu:
'j • dziedziczenia powszechnego ( public ),
• klas bazowych bez składowych prywatnych ( private ),
pomiędzy obiektami klas bazowych i ich klas pochodnych zachodzą następujące konwersje standardowe:
i,; • obiekt klasy bazowej można zastąpić obiektem klasy pochodnej,
• referencję obiektu klasy bazowej można zastąpić referencją obiektu klasy pochodnej,
• wskaźnik obiektu klasy bazowej można zastąpić wskaźnikiem obiektu klasy pochodnej.
Konwersje te są zawsze bezpieczne, ponieważ przy wymienionych założeniach obiekt klasy pochodnej zawiera wszystkie składowe, które występują w obiekcie klasy bazowej. Konwersje odwrotne, wymagające zastąpienia obiektu, referencji czy wskaźnika obiektu klasy pochodnej odpowiednio: obiektem, referencją czy
j wskaźnikiem obiektu klasy bazowej, wymagają zdefiniowania operatora ''I konwersji (punkt 9.8). Obiekt klasy bazowej może bowiem nie zawierać tych wszystkich składowych, które występują w obiekcie klasy pochodnej. Zasady I':
konwersji obiektów klas bazowych i klas pochodnych ilustruje rysunek 1 1.5.
konwersja standardowa
OBIEKT OBIEKT KLASY KLASY POCHODNEJ ~ BAZOWEJ
operator konwersji
Rys. 11.5. Konwersja obiektów
I1.5. Obiekh~ klas pochodnych 221
class Biurko ; //
class Stół i
public: int m nSzerokość ; int m nDługość ; Stół ( int, int ) ;
Stół ( )
operator Biurko ( ) ;
// konstruktor // konstruktor
// operator konwersji
i;
Stół :: Stół ( int nSzerokość, int nDługość ) i
m nSzerokość = nSzerokość ; m nDługość = nDługość ;
i
class Biurko : public Stół (
public: int m_nSzuflady ;
Biurko ( int, int, int ) ; /1 konstruktor
i.
Biurko :: Biurko ( int nSzerokość, int nDługość, int nSzuflady)
Stół ( nSzerokość, nDługość )
m nSzuflady = nSzuflady ;
222 Rozdział ll. Dziedziczenie
Stół :: operator Biurko ( ) (
j return Biurko ( m nSzerokość, m nDługość, 3 ) ;
i
Biurko NoweBiurko ( 40, 60, 5 ) ;
Biurko StareBiurko = NoweBiurko ; // zgodność
Biurko &rBiurko = NoweBiurko ; // zgodność
Biurko 'pBiurko = &NoweBiurko ; // zgodność
Stół NowyStół ( 50, 80 ) ;
Stół StaryStół = NowyStół ; // zgodność
Stół &rStół = NowyStół ; // zgodność
Stół *pStół = &NowyStół ; /1 zgodność
StaryStół = NoweBiurko ; // konwersja standardowa
rStół = NoweBiurko ; // konwersja standardowa
pStół = &NoweBiurko ; // konwersja standardowa
StareBiurko = NowyStół ; // operator konwersji
StareBiurko = Biurko ( NowyStół ) ; // operator konwersji
rBiurko = NowyStół ; // operator konwersji
rBiurko = Biurko ( NowyStół ) ; // operator konwersji
pBiurko = &NowyStół ; // bład
pBiurko = &Biurko ( NowyStół ) ; // operator konwersji
Automatyczna konwersja obiektu (referencji, wskaźnika) klasy pochodnej na
obiekt (referencję, wskaźnik) klasy bazow ej wprowadza jednak pewne ogranicze-
nia. Nie można bowiem po jej dokonaniu odwoływać się do składowych klasy
pochodnej, które nie zostały odziedziczone z klasy bazowej.
Stół *pStolik ;
Biurko Mebel ( 45, 75, 3 ) ;
pStolik = &Mebel ; // konwersja standardowa
I LS. Obiekh~ klas pochodnych 223
int nSzerokość = pStolik -> m_nSzerokość ; // poprawnie int nSzuflady = pStolik -> m nSzuflady ; // błąd
Rozwiązanie tego problemu jest możliwe za pomocą funkcji wirtualnych przedstawionych w następnym rozdziale.
Gdy obiekty, wskaźniki lub referencje klas bazowych i pochodnych są przekazywane jako argumenty funkcji, przy uzgadnianiu typów (klas) argumentów formalnych i aktualnych mogą wystąpić następujące przypadki:
• bezpośrednia zgodność typów (ta sama klasa),
• zgodność uzyskana po konwersji standardowej (klasa pochodna zastępuje klasę bazową),
• zgodność uzyskana po konwersji zadanej w programie za pomocą operatora konwersji lub za pomocą konstruktora,
• niezgodność.
class Drukarka ; class Kserograf {
protected: char *m szFormat ; char *m szToner ; public:
operator Drukarka ( ) ; // operator konwersji class Laser
{ protected:
float m flDługośćFali, m flMoc ; i~
class Drukarka : public Kserograf, public Laser protected:
char *m szSprzęg ;
224 Rozdziaf Il. Dziedziczenie
!: char *m szJęzykSterowania ; public:
Drukarka ( Laser ) ; // konstruktor
i;
Kserograf ksero ; Laser laser ; Drukarka drukarka ; //
void FunkcjaPierwsza ( Kserograf ) ;
E FunkcjaPierwsza ( ksero ) ; // zgodność bezpośrednia ;I f
I FunkcjaPierwsza ( laser ) ; // błąd
FunkcjaPierwsza ( drukarka ) ; // konwersja standardowa ! //
void FunkcjaDruga ( Laser ) ;
FunkcjaDruga ( laser ) ; // zgodność bezpośrednia FunkcjaDruga ( ksero ) ; // błąd
FunkcjaDruga ( druk ) ; // konwersja standardowa i
i //
void FunkcjaTrzecia ( Drukarka ) ; i
FunkcjaTrzecia ( drukarka ) ; // zgodność bezpośrednia FunkcjaTrzecia ( ksero ) ; // operator konwersji
I! FunkcjaTrzecia ( laser ) ; // konwersja za pomocą konstruktora //
Podany sposób uzgadniania typów jest również stosowany przy wyborze wersji funkcji o przeciążonym identyfikatorze, której argumentami są obiekty klas. Gdy
dla danego sposobu uzgadniania możliwy jest wybór więcej niż jednej funkcji, . kompilator sygnalizuje błąd niejednoznaczności.
11.5. Obiekty klas hochodn~~ch 225
Konwersja wskaźników składowych klas bazowych i klas pochodnych
W przypadku wskaźników składowych obiektów (punkt 9.4.4) klas bazowych i klas pochodnych obowiązująnastępujące reguły konwersji:
• wskaźnik składowej obiektu klasy pochodnej można zastąpić wskaźnikiem składowej obiektu klasy bazowej (konwersja standardowa),
• aby zastąpić wskaźnik składowej obiektu klasy bazowej wskaźnikiem składowej obiektu klasy pochodnej, należy zdefiniować operator konwersji. Regułę tę, która jest jakby odwrotnością reguł konwersji obiektów klas bazowych i klas pochodnych, ilustruje rysunek 11.6.
WSKAŻNIK operator konwersji WSKAŻNIK SKŁADOWEJ SKŁADOWEJ KLASY KLASY POCHODNEJ BAZOWEJ
Konwersja standardowa
Rys. 11.6. Konwersja składowych obiektów
class Strunowe
public: int m_nLiczbaStrun ; }~
class Gitara : public Strunowe protected:
char m cAkustyczna ; } ;
int Strunowe :: *pStruny ;
int Gitara :: *pTony ;
pStruny = &Strunowe :: m nLiczbaStrun ;
plony = &Gitara :: m nLiczbaStrun ;
226 Rozc~iał ll. Dziedziczenie
pStruny = plony ; // błąd
plony = pStruny ; // konwersja standardowa
Identyfikacja obiektów 11 ~ ~ Po zdefiniowaniu hierarchii klas pochodnych pew
nej klasy bazowej można, dzięki konwersji standardowej, zastępować obiekty, referencje lub wskaźniki klasy bazowej obiektami, referencjami lub wskaźnikami klas pochodnych. Na przykład, wartością zmiennej zadeklarowanej jako wskaźnik obiektu klasy bazowej może być wskaźnik obiektu dowolnej klasy pochodnej, dziedziczącej z tej klasy bazowej. Powstaje w związku z tym problem, w jaki sposób stwierdzić, do której z klas pochodnych należy obiekt, wskazywany przez wartość zmiennej wskaźnikowej. Problem ten można rozwiązać na dwa sposoby. Po pierwsze do klas pochodnych można wprowadzić skladowe typu i bezpośrednio w programie analizować ich wartości. Druga metoda, polegająca na definiowaniu funkcji wirtualnych przenosi ciężar rozpoznawania klasy pochodnej na kompilator i system wykonawczy. Fccnkcja wirtualna, zdefiniowana w klasie bazowej, jest następnie redefiniowana (przeciążana) w klasach pochodnych. Podczas wykonywania programu wywołanie funkcji wirtualnej może nastąpić za pośrednictwem zmiennej wskaźnikowej, która wskazuje na obiekt jednej z klas pochodnych. System wykonawczy wywołuje wówczas wersję funkcj i wirtualnej, którą zdefiniowano w tej właśnie klasie pochodnej.
11.6.1
Składowe typu
Wprowadzanie składowych typu do definicji klas po
chodnych pozwala na bezpośrednie stwierdzenie, do której klasy pochodnej należy obiekt, wskazywany przez pewną zmienną wskaźnikową lub referencyjną. W celu ustalenia klasy pochodnej należy zastosować ciąg in
77.6. Iderahfikacja obiektów 227
strukcji warunkowych lub instrukcję wyboru. Powoduje to niekiedy znaczne zwiększenie wielkości programu, a ponadto wymaga modyfikowania instrukcji testujących każdorazowo po dodaniu lub usunięciu klasy pochodnej.
class Obuwie // klasa bazowa f
public:
char m cTyp ;
Obuwie ( ) // konstruktor
f
m cTyp = 1 ;
};
class Letnie : public Obuwie // klasa pochodna
f
public:
Letnie ( ) // konstruktor
f
m cTyp = 2 ;
i;
class Zimowe : public Obuwie // klasa pochodna
f
public:
Zimowe( ) // konstruktor
f
m cTyp = 3 ;
i;
228
Rozd=iał Il. Dziedziczenie
class Sandały : public Letnie f
public: Sandały ( ) f
m cTyp = 4 ;
// klasa pochodna
// konstruktor
) );
class Półbuty : public letnie // klasa pochodna f
public: Półbuty ( ) f
// konstruktor
m cTyp = 5 ;
)
}; obuwie ;
Obuwie
Letnie l etnie ;
Zimowe zimowe ;
Sandały sandały ;
Półbuty półbuty ;
Obuwie *pBut ;
Obuwie &rBut = obuwie ;
pBut = &sandały ;
raut = zimowe ;
// obiekty klas
// zmienna wskaźnikowa // zmienna referencyjna
// przykładowe przypisania
l 1.6. Identyfikacja obiektów 229
switch ( pBut -> m cTyp ) i
case 1 : .............. case 2 : .............. case 5 : .............. default : ..............
// instrukcja testująca
// obiekt klasy Obuwie
// obiekt klasy Letnie
// obiekt klasy Półbuty
// obiekt nieznany
i
if ( raut . m cTyp == 3 ) // instrukcja testująca ........... // obiekt klasy Zimowe
Funkcje wirtualne 1 1 .6.2 Fcrnkcje wirtualne definiowane są dla pewnej hierarchii
klas. Jest to rodzina funkcji o takim samym typie wyniku, takim samym identyfikatorze i tej samej sygnaturze (czyli liczbie i typach argumentów) definiowanych w klasie bazowej i w klasach pochodnych. Funkcję wirtualną wyróżnia się, poprzedzając w ciele klasy jej deklarację lub definicję słowem kluczowym virtual. Słowo to musi być użyte w klasie bazowej, będącej korzeniem hierarchii klas, natomiast w klasach pochodnych nie jest konieczne jego powtarzanie.
class DrzewaKrzewy // klasa bazowa
{ protected:
char 'm szNazwaParku, 'm szNazwaGatunkowa ; float m flWysokość ;
public:
virtual void Wyświetl ( ) i
cout « endl « "Drzewa i krzewy rosnące w " « m szNazwaParku « endl ;
230
Rozd~iat ll. Dziedziczenie
void Wspólne ( ) i
cout « endl « m szNazwaGatunkowa « endl « m flWysokość « endl ;
i }>
class Drzewa : public DrzewaKrzewy // klasa pochodna i
char *m szPień ; public:
void Wyświetl ( ) i
Wspólne ( ) ;
cout « m szPień « endl ;
i i>
class Krzewy : public DrzewaKrzewy i
private: chat `m szPokrój ; public:
void Wyświetl ( ) i
Il klasa pochodna
Wspólne ( ) ;
cout « m szPakrćj « end! ;
17.6. Idenhfkacja obiektów 231
W przykładzie tym, w definicji każdej z klas zawarta jest definicja funkcji Wyświetl. W klasie bazowej DrzewaKrzewy funkcja ta została zadeklarowana jako funkcja wirtualna - definicje znajdujące się w klasach pochodnych są więc jej przeciążeniami. Jak widać, wersja funkcji Wyświetl związana z daną klasą powoduje wyprowadzenie na ekran monitora wartości odpowiednich składowych. Dla klasy bazowej jest to m szNazwaParku a dla klas pochodnych są to składowe wspólne m szNazwaGatunkowa i m flWysokość oraz składowe specyficzne m szPień i m szPokrój. Po zadeklarowaniu zmiennej wskaźnikowej:
DrzewaKrzewy *pDrzewoKrzew ;
można nadawać jej wartości będące wskaźnikami obiektów należących do klas DrzewaKrzewy, Drzewa i Krzewy (konwersja standardowa). Gdy po dokonaniu przypisania:
pDrzewoKrzew = new Drzewo ;
za pośrednictwem zmiennej pDrzewoKrzew zostanie zaktywizowana funkcja wirtualna
pDrzewoKrzew -> Wyświetl ( ) ;
to system wykonawczy wywoła wersję tej funkcji zdefiniowaną w klasie Drzewa. Wersja wywoływanej funkcji wirtualnej zależy więc od klasy, do której należy obiekt wskazywany przez zmienną.
pDrzewoKrzew = new DrzewaKrzewy ;
pDrzewoKrzew -> Wyświetl ( ) ; // wersja klasy DrzewaKrzewy pDrzewoKrzew = new Krzewy ( ) ;
pDrzewoKrzew -> Wyświetl ( ) ; // wersja klasy Krzewy
W celu zapamiętania danych o drzewach i krzewach znajdujących się w parku można również utworzyć tablicę, zadeklarowaną jako tablicę wskaźników obiektów klasy DrzewaKrzewy. Wywołanie funkcji wirtualnej za pośrednictwem elementu takiej tablicy powoduje wywołanie wersji funkcji odpowiedniej do klasy obiektu wskazywanego przez ten element.
DrzewaKrzewy' apTabIicaDrzewKrzewów [ 32 ) ; apTabIicaDrzewKrzewów [ 0 ] = new DrzewaKrzewy ; apTabIicaDrzewKrzewów [ 1 ] = new Krzewy ; apTabIicaDrzewKrzewów [ 2 ] = new Drzewa ;
232
Rozdziat ll. D~ied~ic-enie
apTabIicaDrzewKrzewów [ 0 ] -> Wyświetl ( ) ; // wersja klasy DrzewaKrzewy apTabIicaDrzewKrzewów [ 1 ] -> Wyświetl ( ) ; // wersja klasy Krzewy apTabIicaDrzewKrzewów [ 2 ] -> Wyświetl ( ) ; // wersja klasy Drzewa
Funkcje wirtualne umożliwiają rozwiązanie przedstawionego w poprzednim rozdziale problemu dostępu do składowych specyficznych klasy pochodnej za pośrednictwem wskaźnika zadeklarowanego pierwotnie jako wskaźnik obiektów klasy bazowej. W deklaracji funkcji wirtualnej Wyświetl znajdującej się w klasie pochodnej Drzewa wystąpiło odwołanie do składowej specyficznej tej klasy
', m szPień (składowa ta nie jest dziedziczona z klasy bazowej). Podobnie w wersji funkcji wirtualnej Wyświetl zdefiniowanej w klasie Krzewy występuje odwołanie lill
do składowej specyficznej m szPokrój. Wywołania tej funkcji wirtualnej są natomiast realizowane za pośrednictwem zmiennej zadeklarowanej jako wskaźnik obiektów klasy bazowej DrzewaKrzewy.
Funkcje wirtualne mogą być również aktywizowane statycznie za pośrednictwem obiektów lub ich referencji czy wskaźników.
Drzewa brzoza ;
Drzewa &rDrzewo = brzoza ;
Drzewa *pDrzewo = &brzoza ; brzoza . Wyświetl ( ) ;
rDrzewo . Wyświetl ( ) ; pDrzewo -> Wyświetl ( ) ;
brzoza . DrzewaKrzewy :: Wyświetl ( ) ; rDrzewo . DrzewaKrzewy :: Wyświetl ( ) ; pDrzewo -> DrzewaKrzewy :: Wyświetl ( ) ;
Ostatnie trzy instrukcje powodują wykonanie tej wersji funkcji wirtualnej Wyświetl, którą zdefiniowano w klasie bazowej DrzewaKrzewy.
Częstą praktyką jest wywoływanie w definicji funkcji wirtualnej klasy pochodnej wersji tej samej funkcji z jej bezpośredniej klasy bazowej. Dzięki temu na każdym poziomie hierarchii klas (i związanej z nią hierarchii funkcji wirtualnych) realizowane sąoperacje specyficzne dla tego poziomu. Łączna operacja jest ;,
I1.6. Idetyfikacja obiektów 233 więc realizowana jako złożenie operacji wykonywanych na poszczególnych poziomach hierarchii klas.
class MateriałyBudowlane f
public: virtual void Opis ( ) f
cout « " materiał budowlany." « endl ; i
)~ class MateriałyCeramiczne : public MateriałyBudowlane f
public: virtual void Opis ( ) f
cout « " ceramiczny" ; MateriałyBudowlane :: Opis ( ) ; i
).
class Cegła : public MateriałyCeramiczne f
public: virtual void Opis ( ) f
cout « endl « "Cegła to" ; MateriałyCeramiczne :: Opis ( ) ; )
i.
234 Rozd~ial ll. D~iec~iczenie
Cegła cegła ;
Zastosowanie funkcji Opis do obiektu cegła spowoduje utworzenie następującego zdania:
Cegła to ceramiczny materiał budowlany.
Kolejne elementy tego zdania zostały wygenerowane na kolejnych poziomach hierarchii klas.
Funkcje wirtualne nie muszą być przeciążane we wszystkich klasach pochodnych hierarchii dziedziczenia. Dla niektórych klas pewne funkcje nie mają bowiem zastosowania i definiowanie ich byłoby bezcelowe. Zdefiniowanie w klasie pochodnej funkcji o identyfikatorze takim samym jak identyfikator funkcji wirtualnej, występującej w klasie bazowej, powoduje albo przesłonięcie funkcji wirtualnej, albo błąd kompilacji. I tak, gdy funkcja zadeklarowana w klasie pochodnej ma, w porównaniu z funkcją wirtualną taki sam typ wyniku, lecz inną sygnaturę, to następuje przesłonięcie funkcji wirtualnej. Jeżeli natomiast funkcja wirtualna i funkcja zdefiniowana w klasie pochodnej różnią się tylko typem wyniku, to sygnalizowany jest błąd kompilacji.
class Nuta // klasa bazowa
protected:
int m nWysokość ; float m_nCzas ;
public: virtual int Wysoka ( ) // funkcja wirtualna i
return m nWysokość ;
); class Ósemka : public Nuta // klasa pochodna
public: a
11.6. lclenty akacja obiektów 235
int Wysoka ( ) // poprawnie, przeciążenie
return m nWysokość + 1 ;
int Wysoka ( int nWysokość ) // poprawnie, przesłanianie i
int nPośrednik = m nWysokość ;
m nWysokość = nWysokość ;
return nPośrednik ;
i
void Wysoka ( ) // błąd
m nWysokość = 5 ;
Klasy abstrakcyjne 1 1 .6.a7 w hierarchii dziedziczenia klasa bazowa znajdująca się
na najwyższym poziomie często służy jedynie jako generalizacja klas pochodnych. Zawiera ona wtedy wyłącznie wspólne składowe, występujące we wszystkich klasach pochodnych, nie występują w niej natomiast żadne składowe specyficzne. W programie często nie tworzy się obiektów klasy bazowej, są tworzone jedynie obiekty klas pochodnych. Powstaje w związku z tym pytanie, czy w takiej uogólniającej klasie bazowej trzeba zamieszczać pełne definicje funkcji wirtualnych, skoro i tak dła obiektu klasy bazowej funkcje te nie będą nigdy aktywizowane. W języku C++ można uniknąć podawania pełnej definicji funkcji wirtualnej w klasie bazowej, deklarując czystcł funkcję n~irtualncl. Jej definicja zawiera po liście argumentów formalnych operator przypisania i wartość 0. Klasa bazowa, w której zdefiniowano chociaż jedną czystą funkcję wirtualną, zwana jest klasc/ abstrakcyjna. W programie nie wolno tworzyć obiektów klasy abstrakcyjnej, jest ona jedynie generalizacją klas pochodnych.
2,~() Rozdzial ll. Dziedziczenie I~,
Można natomiast deklarować zmienne, których wartościami będą wskaźniki lub referencje klas abstrakcyjnych.
,. ,:j class TransmisjaSzeregowa Il klasa abstrakcyjna protected:
int m nPrędkość, m nLiczbaBitówZnaku ; public:
'virtual char Nadawanie ( char* ) = 0 ; ll czysta funkcja wirtualna )
i class Asynchroniczna : public TransmisjaSzeregowa I
int m_nLiczbaBitówStopu ; char m cParzystość ; public:
char Nadawanie ( char *szTekst ) ;
?, class Synchroniczna : public TransmisjaSzeregowa
char m cSynchronizacja ; char SumaKontrolna ( char* ) ; public:
char Nadawanie ( char "szTekst ) t char cBłąd = 0 ;
char cSuma = SumaKontrolna ( szTekst ) ;
NadajKomunikat ( BudujKomunikat ( szTekst, cSuma ), &cBłąd ) ; return cBłąd ;
) n ).
i
11.6. Identyfikacja obiektów 23%
char Asynchroniczna :: Nadawanie ( char "szTekst ) f
char cBłąd = 0 ; while ( 'szTekst )
NadajZnak ( BudujZnakAsy (*szTekst++, m_nLiczbaBitówStopu, m cParzystość , &cBłąd ) ;
return cBłąd ; i
I }
TransmisjaSzeregowa "pSzeregowa ; pSzeregowa = new Asynchroniczna ;
pSzeregowa -> Nadawanie ( "Halo, to ja" ) ; // wersja klasy Asynchroniczna pSzeregowa = new Synchroniczna ;
s pSzeregowa -> Nadawanie ( "Co tam słychać po drugiej stronie?" ) ;
', // wersja klasy Synchroniczna
Wirtualne destruktory 11.6.4 Jeżeli destruktor zdefiniowany w klasie bazowej jest
poprzedzony słowem kluczowym virtual, to wszystkie destruktory zdefiniowane w klasach pochodnych są traktowane jak przeciążenia tego wirtualnego destruktora. Reguła ta obowiązuje, pomimo tego iż ze względu na zasady nazywania, destruktory zdefiniowane w klasach pochodnych mają różne identyfikatory. Właściwość ta pozwala na aktywizowanie destruktorów klas pochodnych za pośrednictwem zmiennych wskaźnikowych, zadeklarowanych dla klasy bazowej.
class Pojedynek
{ public:
virtualPojedynek ( ) // wirtualny destruktor cout « "Pojedynek zakończony." « endl ; }
i;
238
Rozd<-iał ll. Dziedziczenie
class Zapalczywy : public Pojedynek i
public: char *m szOstatnieSłowa ; -Zapalczywy ( )
cout « m szOstatnieSłowa « endl ;
i )~
class Mściwy : public Pojedynek
public: int m nLiczbaTrafień ;
Mściwy()
cout « "Trafiłem go " « m nLiczbaTrafień « "razy!" « endl ;
}; Zapalczywy *raptus = new Zapalczywy ;
raptus -> m szOstatnieSłowa = "A jednak miałem rację!" ; Mściwy `odwet = new Mściwy ;
odwet -> m nLiczbaTrafień = 13 ; Pojedynek *finał ;
finał = raptus ;
delete finał ; // wersja klasy Zapalczywy finał = odwet ;
delete finał ; // wersja klasy Mściwy
/1.6. Ident~flkacja obiektów 239
Usunięcie obiektu klasy pochodnej powoduje wykonanie najpierw destruktora klasy pochodnej, a potem destruktora klasy bazowej. Toteż efektem dwukrotnego wykonania wyrażenia:
delete finał ; będzie:
A jednak miałem rację! Pojedynek zakończony. Trafiłem go 13 razy! Pojedynek zakończony.