• Nie Znaleziono Wyników

Dziedziczenie wielobazowe

N/A
N/A
Protected

Academic year: 2021

Share "Dziedziczenie wielobazowe"

Copied!
26
0
0

Pełen tekst

(1)

Wprowadzenie do programowanie

Wprowadzenie do programowanie

obiektowego w języku C++

obiektowego w języku C++

Dziedziczenie wielobazowe

Część czwarta

Roman Simiński

roman.siminski@us.edu.pl

www.us.edu.pl/~siminski

Autor Kontakt

(2)

Koncepcja dziedziczenia wielobazowego

Koncepcja dziedziczenia wielobazowego

Dziedziczenie wielobazowe ― o co chodzi?

Dziedziczenie wielobazowe pozwala na tworzenie klas potomnych, dziedziczących z

więcej niż jednej klasy bazowej.

Mając np. dwie, zaimplementowane klasy, można zbudować nową klasę, dziedziczącą

jednocześnie właściwości obu tych klas, co zwykle nie wymaga wprowadzania zmian

w ich kodzie lub zmiany te są nieznaczne.

Samochód Łódka Amfibia Stół Okrąg OkrągłyStół ListBox EditLine ComboBox

(3)

Koncepcja dziedziczenia wielobazowego

Koncepcja dziedziczenia wielobazowego

Za i przeciw dziedziczeniu wielobazowemu

Tak — pozwala programiście budować elastyczne i dobrze odwzorowujące rzeczywistość

hierarchie klas.

Nie — zmusza programistę do większej uwagi, zwiększa złożoność języka oraz

kompilatora.

W wielu językach programowania zrezygnowano z dziedziczenia wielobazowego:

jest np. w językach C++, Pyton,

(4)

Dziedziczenie wielobazowe

Dziedziczenie wielobazowe

syntaktyka

syntaktyka

Anatomia dziedziczenia wielobazowego

class A { public: int aMember; }; class B { public: int bMember; };

class C : public A, public B { public: int cMember; }; . . . C obj; obj.aMember = 0; obj.bMember = 1; obj.cMember = 2;

Obiekt obj klasy C składa się z pól:

• aMember,

• bMember,

• cMember,

a ich kolejność jest taka, jak kolejność

nazw klas w deklaracji klasy C.

(5)

Dziedziczenie wielobazowe

Dziedziczenie wielobazowe

syntaktyka

syntaktyka

Aktywowanie konstruktorów

Kolejność aktywowania konstruktorów dla obiektu klasy pochodnej wynika z kolejności

występowania nazw klas bazowych w deklaracji tej klasy.

Nie jest istotna kolejność ich umieszczenia na liście inicjalizacyjnej konstruktora klasy

pochodnej.

Specjalnie traktowane są wirtualne klasy bazowe — ale o tym później.

Kolejność aktywowania destruktorów jest odwrotna do kolejności stosowanej w

przy-padku konstruktorów.

(6)

Dziedziczenie wielobazowe

Dziedziczenie wielobazowe

syntaktyka

syntaktyka

Aktywowanie konstruktorów

class A { public: A() : aMember( 0 ) {

cout << "Konstruktor: A()" << endl; }

~A() {

cout << "Destruktor: ~A()" << endl; } int aMember; }; class B { public: B() : bMember( 0 ) {

cout << "Konstruktor: B()" << endl; }

~B() {

cout << "Destruktor: ~B()" << endl; }

int bMember; };

(7)

Dziedziczenie wielobazowe

Dziedziczenie wielobazowe

syntaktyka

syntaktyka

Aktywowanie konstruktorów, cd. ...

class C : public A, public B {

public:

C() : A(), B(), cMember( 0 ) {

cout << "Konstruktor: C()" << endl; }

~C() {

cout << "Destruktor: ~C()" << endl; } int cMember; }; int main() { C obj; obj.aMember = 0; obj.bMember = 1; obj.cMember = 2; return 0; }

(8)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Składowe o tej samej nazwie

class A { public: int member; void memberFunction() {}; }; class B { public: int member; void memberFunction() {}; };

class C : public A, public B { public: int cMember; void cMemberFunction(); }; . . . C obj; . . . member member cMember Klasa A Klasa B Klasa C A +member +memberFunction() B +member +memberFunction() C +cMember +cMemberFunction()

(9)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Składowe o tej samej nazwie, cd. ...

obj.cMember = 10; obj.cMemberFunction();

Odwołania jednoznaczne:

obj.member = 100; obj.memberFunction();

Odwołanie niejednoznaczne:

Request for member 'member' is ambiguous, candidates are: int B::member is int A::member

Błąd kompilacji (np. Dev-C):

Member is ambiguous: 'A::member' and 'B::member'

Member is ambiguous: 'A::memberFunction' and 'B::memberFunction'

(10)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Składowe o tej samej nazwie, wykorzystanie operatora zasięgu ::

obj.A::member = 100; obj.B::member = 200;

cout << obj.A::member << endl; obj.A::memberFunction();

cout << obj.B::member << endl; obj.B::memberFunction();

Odwołania jednoznaczne:

C * ptr; ptr = new C; ptr->A::member = 100; ptr->B::member = 200;

cout << ptr->A::member << endl; ptr->A::memberFunction();

cout << ptr->B::member << endl; ptr->B::memberFunction();

delete ptr;

(11)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Składowe o tej samej nazwie, przykład — klasa Amphibian

class Vehicle {

public:

Vehicle( int maxSpeed = 0, int numOfWheels = 0 ) : maxSpeed( maxSpeed ), numOfWheels( numOfWheels ) { } protected: int maxSpeed; int numOfWheels; }; class Boat { public:

Boat( int maxSpeed = 0, int displacement = 0 )

: maxSpeed( maxSpeed ), displacement( displacement ) { } protected: int maxSpeed; int displacement; };

(12)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Składowe o tej samej nazwie, przykład — klasa Amphibian, cd. ...

class Amphibian : public Vehicle, public Boat {

public:

Amphibian( int maxLandSpeed = 0, int maxWaterSpeed = 0, int numOfWheels = 0, int displacement = 0 ) : Vehicle( maxLandSpeed, numOfWheels ),

Boat( maxWaterSpeed, displacement ) {

}

int getMaxLandSpeed() const { return Vehicle::maxSpeed; } int getMaxWaterSpeed() const { return Boat::maxSpeed; } int getNumOfWheels() const { return numOfWheels; } int getDisplacement() const { return displacement; } };

. . .

Amphibian a( 120, 20, 4, 1500 );

cout << endl << "Maks. predk. na ladzie : " << a.getMaxLandSpeed(); cout << endl << "Maks. predk. na wodzie : " << a.getMaxWaterSpeed(); cout << endl << "Liczba kol : " << a.getNumOfWheels();

(13)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Problemów ciąg dalszy

Bezpośrednia klasa bazowa klasy A — klasa, z której klasa A dziedziczy.

Pośrednia klasa bazowa klasy A — klasa, z której dziedziczy bezpośrednia klasa bazowa

Pewien potomek (klasa pochodna) może mieć „rodziców” (klasy bazowe) mających

wspólnego przodka (wspólną klasę bazową).

Powoduje to pewne komplikacje dla potomka.

Samochód Łódka Amfibia ListBox EditLine ComboBox Środek Transportu Środek Transportu Control Control

(14)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie ze wspólną, pośrednią klasą bazową

class A { public: int member; void memberFunction() {}; }; class B : public A { public: int bMember; }; class C : public A { public: int cMember; };

class D : public B, public C {

public:

int dMember; };

(15)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie ze wspólną, pośrednią klasą bazową

W przypadku dziedziczenia wielobazowego po klasach mających wspólna klasę bazową,

klasa pochodna posiada w sobie wielokrotnie składowe występujące we wspólnej klasie

bazowej.

D obj;

obj.member = 100;

obj.memberFunction();

Odwołania niejednoznaczne (błędy kompilacji):

obj : tylko pola obiektu member bMember member cMember dMember Klasa A Klasa A Klasa B Klasa C Klasa D A +member +memberFunction() B +bMember C +cMember D +dMember A +member +memberFunction()

(16)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Usuwanie niejednoznaczności — ponownie operator zakresu

W tym przypadku wystąpienie kopii pól jest semantycznie niepoprawne. Powoduje

to również stratę pamięci.

D obj; obj.B::member = 100; obj.C::memberFunction(); D * ptr = new D; ptr->B::member = 100; ptr->C::memberFunction(); . . .

Problem z wykorzystaniem wskaźników do klasy bazowej

A * ptr1= new D; // Bł dą

A * ptr2= new B; // OK

A * ptr3= new C; // OK

(17)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie w trybie wirtualnym — klasy bazowe virtual

Dziedziczenie w trybie wirtualnym ma na celu wyeliminowanie niejednoznaczności

przy wielokrotnym dziedziczeniu ze wspólnych klas bazowych.

Zadeklarowanie klasy bazowej jako wirtualnej informuje kompilator, że ma umieścić

w klasach pochodnych tylko jedno wystąpienie składowych wirtualnej klasy bazowej.

Samochód

Łódka

Amfibia

ListBox

EditLine

ComboBox

Środek

Transportu

Control

(18)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Anatomia dziedziczenia w trybie wirtualnym

class A { public: int member; void memberFunction() {}; };

class B : virtual public A {

public:

int bMember; };

class C : virtual public A {

public:

int cMember; };

class D : public B, public C {

public:

int dMember; };

Kolejność występowania słów

kluczowych public i virtual

jest dowolna.

(19)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Anatomia dziedziczenia w trybie wirtualnym

D obj;

obj.member = 100; // Teraz tylko jedno wyst pienie tego polaą obj.memberFunction(); // Teraz tylko jedno wyst pienie tej funkcjią . . .

obj.B::member = 100; // Tak mo na było poprzednio i teraz również ż obj.C::memberFunction(); // j.w.

. . .

Odwołania jednoznaczne

Dziedziczenie wirtualne — aktywowanie konstruktorów

Konstruktory klas wirtualnych są aktywowane przed konstruktorami klas

niewirtualnych. Gdy klas wirtualnych jest więcej, kolejność aktywacji odpowiada

kolejności deklaracji.

Jeżeli klasa wirtualna dziedziczy z klasy niewirtualnej, konstruktor tej ostatniej będzie

aktywowany wcześniej.

Konstruktory pozostałych niewirtualnych klas bazowych są aktywowane zgodnie z

kolejnością deklaracji.

(20)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie wirtualne — aktywowanie konstruktorów, cd. ...

Jeżeli w hierarchii klas występują jednocześnie wirtualne i niewirtualne wystąpienia

tej samej klasy, konstruktor tej klasy jest wywoływany raz dla wszystkich wystąpień

wirtualnych oraz raz dla każdego niewirtualnego wystąpienia klasy

class A {}; class B {}; class C: public B, virtual public A {}; class D: public B, virtual public A {}; class E: public C, virtual public D {}; class A {};

class B: virtual public A {}; class C: virtual public A {}; class D: public A {}; class E: public B, public C, public D {}; C D E A B B B C E A A D

(21)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie wirtualne — aktywowanie konstruktorów, cd. ...

class A {

public:

A() : a( 0 ) { cout << "Default A: a = 0" << endl; } A( int a ) : a( a )

{

cout << "General A: a = " << a << endl; }

int a; };

class B: virtual public A {

public:

B() : A() { cout << "Default B" << endl; } B( int a, int b ) : A( a ), b( b )

{

cout << "General B: b = " << b << endl; }

int b; };

(22)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Dziedziczenie wirtualne — aktywowanie konstruktorów, cd. ...

class C: virtual public A {

public:

C() : A() { cout << "Default C"; } C( int a, int c ) : A( a ), c( c ) {

cout << "General C: c = " << c << endl; }

int c; };

class D: public B, public C {

public:

D() : B(), C() { cout << "Default D"; }

D( int a, int b, int c, int d ) : B( a, b ), C( a, c ), d( d ) {

cout << "General D: d = " << d << endl; }

int d; };

. . .

D obj( 1, 2, 3, 4 );

cout << "a: " << obj.a << endl; cout << "b: " << obj.b << endl; cout << "c: " << obj.c << endl; cout << "d: " << obj.d << endl;

(23)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Niby wszystko w porządku ale … :-/

Dlaczego pole a wirtualnej klasy bazowej A ma

wartość 0?

Przecież:

D obj( 1, 2, 3, 4 );

Co się dzieje?

Dlaczego pole a ma wartość 0? Jak nadać mu właściwą wartość?

Przecież:

Normalnie programista nie musi się przejmować inicjalizacją składowych

odziedziczonych po pośrednich klasach bazowych (czyli A dla klasy D).

Zajmują się tym konstruktory bezpośrednich i występujących wcześniej w hierarchii

pośrednich klas bazowych (czyli B i C dla klasy D).

Istnieją jednak dwie drogi realizacji inicjalizacji — poprzez klasę B lub poprzez klasę C.

Ponownie mamy niejednoznaczność, która klasa — B czy C — jest odpowiedzialna za

inicjalizację pola a?

(24)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Wirtualne klasy bazowe wymagają specjalnej inicjalizacji

Z powodu niejednoznaczności, w przypadku dziedziczenia wielobazowego z klasą

wirtualną:

Klasa pochodna musi zainicjalizować jawnie składowe wirtualnej klasy bazowej.

class D: public B, public C {

public: . . .

D( int a, int b, int c, int d ) : A( a ), B( a, b ), C( a, c ), d( d ) {

cout << "General D: d = " << d << endl; }

};

Konstruktor klasy pochodnej jawnie inicjalizuje

składowe wirtualnej klasy bazowej, aktywując jej

konstruktor na liście inicjalizującej.

(25)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Wirtualne klasy bazowe wymagają specjalnej inicjalizacji

A co z aktywacją konstruktorów klasy A z listy inicjalizacyjnej klas B i C? Konstruktory

te są:

W omawianym przypadku kompilator zajmuje się modyfikowaniem działania

konstruktorów.

Obowiązkiem programisty jest:

Niezbędne, gdy są stworzone samodzielne obiekty klas B i C, wtedy działają

normalnie.

Jeżeli działają dla obiektu klasy potomnej — obiektu klasy D — ignorowana jest

inicjalizacja składowych wirtualnej klasy bazowej A.

Wiedzieć, że w budowanej przez niego hierarchii klas występuje dziedziczenie

wielobazowe z wirtualnymi klasami bazowymi.

Inicjalizować w klasie pochodnej składowe odziedziczone, nawet pośrednio, po

(26)

Problemy z dziedziczeniem wielobazowym

Problemy z dziedziczeniem wielobazowym

Problemów z dziedziczeniem wielobazowym jest wiele.

Dlatego w wielu językach obiektowych zrezygnowano z tego rodzaju dziedziczenia,

wykorzystując tylko dziedziczenie jednobazowe.

Cytaty

Powiązane dokumenty

Stwórz klasę pochodną Pies dziedziczącą po klasie Ssak z dodatkowym polem imie (char*) oraz metodą Mow().. Klasa Pies będzie klasą bazową dla klasy Husky, która ma

Intuicyjne rozumienie jest proste - pochodna funkcji opisuje tempo zmiany danej

Intuicyjne rozumienie jest proste - pochodna funkcji opisuje tempo zmiany danej (nachylenie)

Skutki prawne odrzucenia spadku w przypadku dziedziczenia ustawowego oraz dziedziczenia testamentowego.. Stwierdzenie nabycia spadku i jego skutki prawne, właściwość

Tworzenie rachunku – nowy typ klasy typu TProdukt4, pochodny klas typu TProdukt2 i TProdukt3 – dziedziczenie dwubazowe... Tworzenie rachunku – nowy typ klasy typu

Dziedziczenie wielobazowe bez powtórzeń – klasa TProdukt1 bez zmian, natomiast TProdukt2 w liście dziedziczenia ma. klasę TProdukt1, która nie jest juŜ

Przykładowo mając dwie, zaimplementowane klasy, można zbudować nową klasę, która dziedziczy.. jednocześnie właściwości obu

Składowe publiczne klasy bazowej są odziedziczone jako publiczne, a składowe chronione jako chronione.. Dziedziczenie chronione - składowe publiczne są dziedziczone jako