08.Klasy i funkcje wirtualne (2) , KLASY I FUNKCJE WIRTUALNE




8. Klasy i funkcje wirtualne

Dziedziczenie mnogie moe by rodkiem dla organizacji bibliotek wokó prostszych klas z mniejsz liczb zalenoci pomidzy klasami, ni w przypadku dziedziczenia pojedynczego. Gdyby ograniczy dziedziczenie do pojedynczego, to kada biblioteka byaby jednym drzewem dziedziczenia, w ogólnoci bardzo wysokim i rozgazionym. Mechanizm dziedziczenia mnogiego pozwala budowa biblioteki w postaci *lasu mieszanego”, w którym drzewa i grafy dziedziczenia mog mie zmienn liczb poziomów i rozgazie. Z przeprowadzonej w r. 7 dyskusji wynika, e takie struktury mona tworzy stosunkowo atwo, gdy klasa pochodna dziedziczy wasnoci kilku niezalenych (rozcznych) klas bazowych, nie majcych wspólnej superklasy. Jeeli jednak bezporednie klasy bazowe danej klasy pochodnej s zalene, naley zastosowa omówione niej mechanizmy jzykowe.

8.1. Wirtualne klasy bazowe

Przedstawiony w p.7.3 przykad ilustruje niejednoznacznoci, jakie mog si pojawi w hierarhii dziedziczenia, gdy klasa pochodna dziedziczy t sam klas bazow kilkakrotnie, idc po rónych krawdziach grafu dziedziczenia. Odwoania do elementów skadowych takiej klasy bazowej s wówczas moliwe, ale kopotliwe (np. obiekt.Pochodna1::a). Jzyk C++ oferuje tutaj mechanizm, dziki któremu “klasy siostrzane” wspódziel informacj (w tym przypadku jeden obiekt wspólnej klasy bazowej) bez wpywu na inne klasy w grafie dziedziczenia. Mechanizm ten polega na potraktowaniu wspólnej klasy bazowej jako klasy wirtualnej w klasach “siostrzanych”, a przywouje si go, piszc sowo kluczowe virtual przed lub po specyfikatorze dostpu, a przed nazw klasy bazowej. Wirtualno wspólnej klasy bazowej jest wasnoci stosowanego schematu dziedziczenia, a nie samej klasy, która poza tym niczym si nie róni od klasy niewirtualnej. Jeeli przy dziedziczeniu mnogim klasa pochodna dziedziczy t sam klas bazow jako wirtualn i * idc po innej gazi * jako niewirtualn, to oczywicie niejednoznacznoci nie usuniemy. Ilustracj tego jest rysunek 8-1, który pokazuje schemat dziedziczenia z wirtualnymi i niewirtualnymi klasami bazowymi.

0x01 graphic

Rys. 8-1 Dziedziczenie mnogie z wirtualnymi klasami bazowymi

W prezentowanym grafie dziedziczenia leca najniej w hierarchii klasa pochodna Z dziedziczy cechy szeciu swoich klas bazowych, przy czym klasy F, C, D i E s jej bezporednimi klasami bazowymi, za A i B * porednimi. Klasy B i E wspódziel jeden obiekt klasy A, poniewa klasa A jest w kadej z nich deklarowana jako wirtualna klasa bazowa. Natomiast kady obiekt klas C i D bdzie zawiera wasn kopi zmiennych skadowych klasy A. W rezultacie kady obiekt klasy Z bdzie zawiera trzy kopie zmiennych skadowych klasy A: jedn przez dwie gazie wirtualne (przez E i B/F) i po jednej z gazi C i D.

Pokazany schemat mona opisa przykadowymi deklaracjami:

class A {

public:

void f() { cout << "A::f()\n"; }

};

class B: virtual public A { };

class c: public A { };

class D: public A { };

class E: virtual public A { };

class F: public B { };

class Z: public F, public C, public D, public E { };

Gdyby zadeklarowa obiekt klasy Z:

Z obiekt;

to kade bezporednie wywoanie funkcji f() z tego obiektu

Z.f();

bdzie niejednoznaczne, a wic bdne.

Wywoania funkcji f() mona uczyni jednoznacznymi, odwoujc si do niej poprzez obiekty klas porednich, które zawieraj dokadnie po jednej kopii obiektu klasy A:

obiekt.C::f();

obiekt.D::f();

obiekt.E::f();

obiekt.F::f();

Niejednoznaczne bdzie równie wywoanie za pomoc wskanika do klasy Z:

Z* wsk = new Z;

wsk->f();

chocia i w tym przypadku moemy woa funkcj f() poprzez adresy obiektów klas porednich:

wsk->C::f();

wsk->D::f();

wsk->E::f();

wsk->F::f();

Wszystkie powysze wywoania porednie maj skadni raczej mao zachcajc. Gdyby w klasie A zadeklarowa zmienne skadowe, to odwoania do nich byyby podobne.

Oczywistym sposobem usunicia niejednoznacznoci z dyskutowanego schematu byoby zadeklarowanie klasy A jako wirtualnej klasy bazowej w pozostaych klasach porednich, tj. C i D. Takie wanie zaoenie przyjto w prezentowanym niej programie, który korzysta ze znacznie prostszego schematu dziedziczenia.

Przykad 8.1.

Schemat dziedziczenia: Bazowa

/ \

/ \

Pochodna1 Pochodna2

\ /

\ /

DwieBazy

#include <iostream.h>

class Bazowa {

public:

Bazowa(): a(0) {}

int a;

};

class Pochodna1: virtual public Bazowa {

public:

Pochodna1(): b(0) {}

int b;

};

class Pochodna2: virtual public Bazowa {

public:

Pochodna2(): c(0) {}

int c;

};

class DwieBazy: public Pochodna1, public Pochodna2 {

public:

DwieBazy() {}

int iloczyn() { return a*b*c; }

};

int main() {

DwieBazy obiekt;

obiekt.a = 4; obiekt.b = 5; obiekt.c = 6;

cout << "Iloczyn wynosi: "

<< obiekt.iloczyn() << endl;

return 0;

}

Dyskusja. Instrukcja deklaracji DwieBazy obiekt; wywouje konstruktor domylny DwieBazy() {}. Konstruktor ten najpierw wywouje konstruktor Bazowa(){ a = 0; }, a nastpnie konstruktory domylne Pochodna1() i Pochodna2(). W rezultacie obiekt klasy DwieBazy bdzie zawiera po jednym pod-obiekcie klas Bazowa, Pochodna1 i Pochodna2.

Pozostaa cz programu nie wymaga obszerniejszego komentarza. Zauwamy jedynie, e w definicji funkcji iloczyn() wyraenie a*b*c jest równowane:

Bazowa::a*Pochodna1::b*Pochodna2::c.

Równie poprawny byby zapis

obiekt.Bazowa::a, ale duszy od obiekt.a.

Jeeli wirtualna klasa bazowa zawiera konstruktory, to jeden z nich musi by konstruktorem domylnym, albo konstruktorem z inicjalnymi wartociami domylnymi dla wszystkich argumentów. Konstruktor domylny bdzie woany bez argumentów, jeeli aden konstruktor klasy bazowej nie jest wywoywany jawnie z listy inicjujcej konstruktora klasy pochodnej. Ponadto dla wirtualnej klasy bazowej obowizuj nastpujce reguy: