d7z3zi
Clasele derivate dau posibilitatea de a se defini intr-un mod simplu,
eficient si flexibil clase noi prin adaugarea unor functionalitati claselor
deja existente, fara sa fie necesara reprogramarea sau recompilarea acestora.
Existenta claselor derivate permite exprimarea relatiilor ierarhice intre
conceptele pe care clasele le reprezinta si asigura o interfata comuna pentru
mai multe clase diferite.
De exemplu, entitatile cerc, triunghi, dreptunghi, sunt corelate intre
ele prin aceea ca toate sunt forme geometrice, deci ele au in comun conceptul
de forma geometrica. Pentru a reprezenta un cerc, un triunghi sau un dreptunghi,
intr-un program, trebuie ca aceste clase, care reprezinta fiecare forma
geometrica in parte, sa aiba in comun clasa care reprezinta in
general o forma geometrica. O clasa care asigura proprietati comune mai multor
altor clase se defineste ca o clasa de baza. Clasele derivate mostenesc de la
o clasa de baza toate caracteristicile acesteia, la care adauga alte caracteristici
noi, specifice ei.
5.1 Clase derivate
Se considera un program care descrie organizarea personalului unei institutii
fara folosirea claselor derivate. O clasa numita Angajat detine date si functii
referitoare la un angajat al institutiei:
class AngajatA char * nume; float salariu; public:
Angajat();
Angajat(char *n, int s, float sal);
Angajat(Angajat& r); void display();
S;
Angajat::display()A cout << nume << “ ” << salariu << endl;
S
Diferite categorii de angajati necesita date suplimentare fata de cele definite
in clasa Angajat, corespunzatoare postului pe care il detin. De
exemplu, un sef de sectie (administator) este un angajat (deci sunt necesare
toate datele care descriu aceasta calitate) dar mai sunt necesare si alte informatii,
de exemplu precizare sectiei pe care o conduce. De aceea, clasa Administator
trebuie sa includa un obiect de tipul Angajat , la care se adauga alte date:
class AdministratorA
Angajat ang; int sectie; public: void display();
S;
Posibilitatea de a include intr-o clasa date descrise intr-o alta
clasa are in limbajele obiect-orientate un suport mai eficient, mai flexibil
si mai simplu de utilizat decat simpla includere a unui obiect din tipul
dorit: derivarea claselor.
Un administrator este un angajat, de aceea clasa Administrator se poate construi
prin derivare din clasa Angajat astfel:
class Administrator : public AngajatA int sectie; public: void display();
S
Aceasta operatie care se poate reprezenta schematic prin indicarea unei derivari
cu o sageata (arc directionat) de la clasa a derivata la clasa de baza.
clasa de baza Angajat
clasa derivata Administrator
In general, derivarea unei clase se specifica in felul urmator:
class nume_derivata : specificator_acces nume_baza A
// corpul clasei
S;
Specificatorul de acces poate fi unul din cuvintele-cheie: public, private,
protected. Cand o clasa este derivata dintr-o clasa de baza, clasa derivata
mosteneste toti membrii clasei de baza (cu exceptia unora: constructori, destructor
si functia operator de asignare).
Tipul de acces din interiorul clasei derivate la membrii clasei de baza este
dat de specificatorul de acces. Daca nu este indicat, specificatorul de acces
este implicit private
Cand specificatorul de acces este public, toti membrii de tip public ai
clasei de baza devin membri de tip public ai clasei derivate; toti membrii protected
ai clasei de baza devin membri protected ai clasei derivate. Membrii private
ai clasei de baza raman private in clasa de baza si nu sunt accesibili
membrilor clasei derivate. Aceasta restrictie de acces ar putea pare surprinzatoare,
dar, daca s-ar permite accesul dintr-o clasa derivata la membrii private ai
clasei de baza, notiunile de incapsulare si ascundere a datelor nu ar
mai avea nici o semnificatie. Intr-adevar, prin simpla derivare a unei
clase, ar putea fi accesati toti membrii clasei respective.
Metoda cea mai adecvata de acces la membrii private clasei de baza din clasa
derivata este prin utilizarea functiilor membre publice ai clasei de baza.
De exemplu, nu se poate implementa functia display() din clasa Administrator
prin accesarea membrilor private ai clasei Angajat:
void Administrator::display()A cout << nume << “ ”<< salariu << endl; //
error cout << sectie << endl;
S
In schimb, se poate folosi functia membra publica display() a clasei
Angajat:
void Administrator::display()A
Angajat::display(); cout << sectie << endl;
S
Redefinirea functiei display() in clasa derivata ascunde functia cu acelasi
nume din clasa de baza, de aceea este necesara calificarea functiei cu numele
clasei de baza folosind operatorul de rezolutie: Angajat::display().
Trebuie facuta precizarea ca functia display() din clasa derivata nu supraincarca
ci redefineste (redefines) functia cu acelasi nume din clasa de baza.
Un membru al unei clasei derivate care are acelasi tip si nume cu un membru
al clasei de baza il redefineste pe cel din clasa de baza.
Din clasa de baza (in functii membre ale acesteia si pentru obiecte din
clasa de baza) este accesat membrul din clasa de baza.
Din clasa derivata (in functii membre ale acesteia sau pentru obiecte
din clasa derivata) este accesat membrul redefinit in clasa derivata.
Se spune ca membrul din clasa de baza este ascuns (hidden) de membrul redefinit
in clasa derivata. Un membru ascuns din clasa de baza poate fi totusi
accesat daca se foloseste operatorul de rezolutie (::) pentru clasa de baza.
De exemplu:
class baza A public: int a, b;
S;
class derivata A public: int b, c; // b este redefinit
S; void fb() A derivata d; d.a = 1; // a din baza d.baza::b = 2; // b din baza d.b = 3; // b din derivata d.c = 4; // c din derivata
S
Este posibila mostenirea din mai multe clase de baza. De exemplu:
class X : public Y, public Z, ….public W A
// corpul clasei
S;
Evident, specificatorii de acces pot sa difere (pot fi oricare din public,
private, protected). Mostenirea multipla va fi reluata intr-o subsectiune
urmatoare.
O clasa de baza se numeste baza directa, daca ea este mentionata in lista
de clase de baza. O clasa de baza se numeste baza indirecta daca nu este baza
directa dar este clasa de baza pentru una din clasele mentionate in lista
claselor de baza. Intr-o clasa derivata se mostenesc atat membrii
bazelor directe cat si membrii bazelor indirecte si, de asemenea, se pastreaza
mecanismul de redefinire si ascundere a membrilor redefiniti. De exemplu:
class A A public: void f();S; class B : public A A S; class C : public B Apublic: void f(); S; void f() A
C::f(); // apel f() din clasa C
A::f(); // apel f() din clasa A
B::f(); // apel f() din clasa A,
// deoarece nu este redefinita in B
S
Prin mostenire multipla si indirecta se pot crea ierarhii de clase care se
reprezinta prin grafuri aciclice directionate.
5.1.1 Constructori si destructori in clasele derivate
Constructorii si destructorii sunt functii membre care nu se mostenesc. La
crearea unei instante a unei clase derivate (obiect) se apeleaza implicit mai
intai constructorii claselor de baza si apoi constructorul clasei
derivate. Ordinea in care sunt apelati constructorii claselor de baza
este cea din lista claselor de baza din declaratia clasei derivate. Constructorii
nu se pot redefini pentru ca ei, in mod obligatoriu, au nume diferite
(numele clasei respective).
La distrugerea unui obiect al unei clase derivate se apeleaza implicit destructorii
in ordine inversa: mai intai destructorul clasei derivate,
apoi constructorii claselor de baza, in ordinea inversa celei din lista
din declaratie.
La instantierea unui obiect al unei clase derivate, dintre argumentele care
se transmit constructorului acesteia o parte sunt utilizate pentru initializarea
datelor membre ale clasei derivate, iar alta parte sunt transmise constructorilor
claselor de baza. Argumentele necesare pentru initializarea claselor de baza
sunt plasate in definitia (nu in declaratia) constructorului clasei
derivate.
Un exemplu simplu, in care constructorul clasei derivate D transfera constructorului
clasei de baza B un numar de k argumente arata astfel:
class BA
//…………………. public:
B(tip1 arg1,…,tipk argk);
S; class D:public B A
//…………………. public:
D(tip1 arg1, …,tipk argk,…,tipn argn);
S;
D::D(tip1 arg1, …,tipk argk, ….…,tipn argn)
:B((tip1 arg1,……,tipk argk);
A
// initialzare date membre clasa derivata
S
Daca nici clasa de baza nici clasa derivata nu au definiti constructori, compilatorul
genereaza cate un constructor implicit pentru fieccare din clase si-i
apeleaza in ordinea baza, apoi derivata.
O clasa derivata trebuie sa aiba prevazut cel putin un constructor in
cazul in care clasa de baza are un constructor care nu este implicit.
In exemplul urmator se completeaza cele doua clase Angajat si Administrator
cu constructorii si destructorii necesari.
? Exemplul 5.1
class Angajat A char *nume; float salariu; public:
Angajat(const char *n, float sal);
Angajat(Angajat& r);
IAngajat() A cout << "Destructor Baza" << endl; delete nume;
S void display() const A cout << "Nume: " << nume
<< " Salariu: " << salariu << endl ;
S
S;
Angajat::Angajat(const char *n, float sal) A cout << "Constructor baza" << endl; int size = strlen(n); nume = new charasize+1i; strcpy(nume, n); salariu = sal;
S
Angajat(Angajat &r)A cout << "Constructor copiere baza\n"; int size = strlen(r.nume); nume = new charasize+1i; strcpy(nume, r.nume); salariu = r.salariu;
S class Administrator : public Angajat A int sectie; public:
Administrator(const char *n, float sal, int sec);
Administrator(Administrator& r);
IAdministrator() A cout << endl << "Destructor derivata"<< endl;
S void display() const;
S;
Administrator::Administrator(const char *n, float sal, int sec)
:Angajat(n, sal)
A sectie = sec; cout << "Constructor derivata\n";
S void Administrator::display() const A cout << "Sef sectie: " << sectie << endl;
Angajat::display();
S
Administrator::Administrator(Administrator& r)
:Angajat(r)
A cout << "Constructor copiere derivata\n"; sectie = r.sectie;
S void fa1()A
Angajat a1("Ionescu", 2000.0f); a1.display(); // executa Angajat::display()
Administrator m1("Popescu", 3000.0f, 1); m1.display(); // executa Administrator::display()
S
In functia fa1() sunt create doua obiecte, obiectul a1 din clasa Angajat
si obiectul m1 din clasa Administrator. Doua din argumentele de apel ale constructorului
obiectului m1 sunt transferate constructorului clasei de baza (argumentele const
char* n si float sal).
La executia functiei fa1() sunt afisate mesajele:
Constructor baza
Nume: Ionescu Salariu: 2000
Constructor baza
Constructor derivata
Sef sectie: 1
Nume: Popescu Salariu: 3000
Destructor derivata
Destructor baza
Destructor baza
Aceste mesaje evidentiaza ordinea de apel a constructorilor si destructorilor
clasei derivate, precum si selectia functiei display() redefinita in clasa
derivata.
Pentru cele doua clase din Exemplul 5.1 au fost definiti si constructorii de
copiere corespunzatori. Se poate remarca ca argumentul constructorului de copiere
al bazei este o referinta la un obiect derivat:
Administrator::Administrator(Administrator& r)
:Angajat(r) A….S
Acest lucru este posibil deoarece conversia de la o referinta la clasa derivata
Administrator poate fi convertita fara ambiguitate in referinta la clasa
de baza Angajat. Fie functia:
void fa2()A
Administratoe m1(“Popescu, 3000.0f, 1);
Administrator m2(m1); m2.display();
S
Obiectul m2 este creat printr-un constructor de copiere: este apelat mai intai
constructorul de copiere al clasei de baza, apoi constructorul de copiere al
clasei derivate. Mesajele afisate la executia functiei fa2() sunt urmatoarele:
Constructor baza
Constructor derivata
Constructor copiere baza
Constructor copiere derivata
Sef sectie: 1
Nume: Popescu Salariu: 3000
Destructor derivata
Destructor baza
Destructor derivata
Desctructor baza
Aceasta este situatia de executie corecta, deoarece au fost definiti corect
constructorii de copiere in clasa de baza si in clasa derivata.
Situatie de executie eronata apare daca nu se defineste constructorul de copiere
in clasa de baza, deoarece in aceasta clasa exista date alocate
dinamic. Daca se defineste corect constructorul clasei de baza Angajat, dar
nu se defineste constructor de copiere in clasa derivata Administrator,
executia este totusi corecta, deoarece constructorul de copiere implicit creat
de compilator pentru clasa derivata Administrator apeleaza constructorul de
copiere definit al clasei de baza Angajat care previne copierea membru cu membru.
Pe exemplul de mai sus se pot verifica toate aceste situatii diferite care pot
sa apara in legatura cu definirea constructorilor de copiere.
In ceea ce privesc destructorii, se poate observa si din exemplele date
ca nu se mostenesc destructorii claselor de baza, dar la distrugerea unui obiect
derivat este apelat mai intai destructorul clasei derivate si apoi
destructorul clasei de baza. Acest lucru este valabil atat in cazul
in care au fost definiti destructori in baza si derivata, cat
si pentru destructorii impliciti generati de compilator. Dat fiind ca destructorii
pot fi declarati functii virtuale, cateva aspecte relativ la comportarea
destructorilor virtuali vor fi prezentate in subsectiunea 5.4
5.2 Controlul accesului la membrii clasei de baza
Accesul la membrii clasei de baza mosteniti in clasa derivata este controlat
de specificatorul de acces (public, protected, private) din declaratia clasei
derivate.
O regula generala este ca, indiferent de specificatorul de acces declarat la
derivare, datele de tip private in clasa de baza nu pot fi accesate dintr-o
clasa derivata. O alta regula generala este ca, prin derivare, nu se modifica
tipul datelor in clasa de baza.
Un membru protected intr-o clasa se comporta ca un membru private, adica
poate fi accesat numai de membrii acelei clase si de functiile de tip friend
ale clasei. Diferenta intre tipul private si tipul protected apare in
mecanismul de derivare: un membru protected al unei clase mostenita ca public
intr-o clasa derivata devine tot protected in clasa derivata, adica
poate fi accesat numai de functiile membre si friend ale clasei derivate si
poate fi transmis mai departe, la o noua derivare, ca tip protected.
5.2.1 Mostenirea de tip public a clasei de baza
Daca specificatorul de acces din declaratia unei clase derivate este public,
atunci:
• Datele de tip public ale clasei de baza sunt mostenite ca date de tip
public in clasa derivata si deci pot fi accesate din orice punct al domeniului
de definitie al clasei derivate.
• Datele de tip protected in clasa de baza sunt mostenite protected
in clasa derivata, deci pot fi accesate numai de functii membre si friend
ale clasei derivate.
? Exemplul 5.2
In acest exemplu sunt prezentate si comentate cateva din situatiile
de acces la membrii clasei de baza din clasa derivata atunci cand specificatorul
de acces este public.
#include <iostream.h> class baza A int a; protected: int b; public: int c; void SetA(int x)Aa = x; cout << "SetA din baza\n";S void SetB(int y)Ab = y; cout << "SetB din baza\n";S void SetC(int z)Ac = z; cout << "SetC din baza\n";S
S; class derivata:public baza A int d; public: void SetA(int x) A a = x; // eroare, a este private
S void SetB(int y) A b = y; cout << "SetB din derivata\n";
S void SetC(int z) A c = z; cout << "SetC din derivata\n";
S
S; void fb()A derivata D;
D.a = 1; // eroare, a este private in baza
D.SetA(2); // corect se apeleaza baza::SetA()
D.b = 3; // eroare, b este protected
D.baza::SetB(5); // corect, baza::SetB este public
D.SetB(4); // corect, derivata::SetB este public
D.c = 6; // corect, c este public
D.baza::SetC(7); // corect, baza::SetC este public
D.SetC(8); // corect, derivata::SetC este public
S
Daca se comenteaza liniile de program care provoaca erori si se executa functia
fb(), se obtin urmatoarele mesaje la consola:
SetA din baza
SetB din baza
SetB din derivata
SetC din baza
SetC din derivata
5.2.2 Mostenirea de tip protected a clasei de baza
Daca specificatorul de acces din declaratia clasei derivate este protected,
atunci toti membrii de tip public si protected din clasa de baza devin membri
protected in clasa derivata. Bineinteles, membrii de tip private
in clasa de baza nu pot fi accesati din clasa derivata.
Se reiau clasele din exemplul precedent cu mostenire protected:
class derivata:protected baza A
// acelasi corp al clasei
S;
Atunci, in functia fb() sunt identificate ca erori toate apelurile de
functii ale clasei de baza pentru un obiect derivat, precum si accesul la variavila
c din baza:
void fb()A derivata D;
D.a = 1; // eroare, a este private in baza
D.SetA(2); // eroare, baza::SetA()este protected
D.b = 3; // eroare, b este protected
D.baza::SetB(5); // eroare, baza::SetB este prot
D.SetB(4); // corect, derivata::SetB este public
D.c = 6; // eroare, c este protected
D.baza::SetC(7); // eroare, baza::SetC este prot
D.SetC(8); // corect, derivata::SetC este public
S
Astfel ca, daca se comenteaza toate liniile din functia fb() care produc erori,
la executia acesteia se afiseaza urmatoarele rezultate:
SetB din derivata
SetC din derivata
Reiese foarte pregnant, ca in mostenirea protected a unei clase de baza,
nu mai pot fi accesati din afara clasei derivate nici unul dintre membrii clasei
de baza.
5.2.3 Mostenirea de tip private a clasei de baza
Daca specificatorul de acces din declaratia clasei derivate este private, atunci
toti membrii de tip public si protected din clasa de baza devin membri de tip
private in clasa derivata si pot fi accesati numai din functii membre
si friend ale clasei derivate. Din nou trebuie amintit ca membrii de tip private
in clasa de baza nu pot fi accesati din clasa derivata.
Din punct de vedere al clasei derivate, mostenirea de tip private este echivalenta
cu mostenirea de tip protected. Si, intr-adevar, daca modificam clasa
derivata din Exemplul 5.2 astfel:
class derivata : private baza A
// acelasi corp al clasei
S;
mesajele de erori de compilare si de executie ale functiei fb() sunt aceleasi
ca si in mostenirea protected.
Ceea ce diferentiaza mostenirea de tip private fata de mostenirea de tip protected
este modul cum vor fi trasmisi mai departe, intr-o noua derivare, membrii
clasei de baza. Toti membrii clasei de baza fiind mosteniti de tip private,
o noua clasa derivata (care deci mosteneste indirect clasa de baza) nu va mai
putea accesa nici unul din membrii clasei de baza.
5.2.4 Modificarea individuala a accesului la membrii clasei de baza
Se pot modifica drepturile de acces la membrii mosteniti din clasa de baza
prin declararea individuala a tipuli de acces. Aceste drepturi nu pot depasi,
insa, caracterul private a membrilor clasei de baza. De exemplu, se reiau
astfel cele doua clase din Exemplul 5.2:
#include <iostream.h> class baza A int a; protected: int b; public: int c;
//……………….
S; class derivata:private baza A int d; public: baza::a; //eroare, a nu poate fi declarat public baza::b; // corect baza::c; // corect
//………………………
S; void fb()A derivata D;
D.a = 1; // eroare, a este private
D.b = 5; // corect, b este public
D.c = 6; // corect, c este public
D.SetB(5); // corect, derivata::SetB este public
D.SetC(8); // corect, derivata::SetC este public
S
Variabilele b si c din clasa baza au fost transformati in membri publici
ai clasei derivata prin declaratiile: baza::b; baza::c; in zona de declaratii
de tip public a clasei derivata.
5.3 Mostenirea multipla
Asa cum s-a aratat in subsectiunea 5.1, o clasa poate avea mai multe
clase de baza directe, daca acestea sunt specificate in declaratia clasei.
In exemplul urmator este prezentata clasa derivata care mosteneste doua
clase de baza, baza1 si baza2.
? Exemplul 5.3
#include <iostream.h> class baza1A protected: int x; public: baza1(int i) A cout << "Constructor baza1\n"; x = i;
S
Ibaza1()A cout <<"Destructor baza1\n";
S int getx()Areturn x;S
S; class baza2A protected: int y; public: baza2(int j)A cout << "Constructor baza2\n"; y = j;
S int gety()Areturn y;S
Ibaza2()A cout <<"Destructor baza2\n";
S S; class derivata:public baza1, public baza2
A int d; public: derivata (int i, int j);
Iderivata()A cout << "Destructor derivata\n";
S
S; derivata::derivata(int i, int j): baza1(i), baza2(j)
A cout << "Constructor derivata\n";
S void fbm()A derivata D(3,4); cout << D.getx() << " "<< D.gety() << endl;
S
In functia fbm() este creat un obiect D de clasa derivata. Constructorul
clasei derivata transfera cate un argument constructorilor fiecarei clase
de baza, care initializeaza datele membre x si y. La executia functiei fbm()
se afiseaza urmatoarele mesaje, care indica ordinea in care sunt apelati
constructorii si destructorii clasei derivate si a claselor de baza.
Constructor baza 1
Constructor baza 2
Constructor derivata
3 4
Destructor derivata
Destructor baza2
Destructor baza 1
5.3.1 Clase de baza virtuale
Intr-o mostenire multipla este posibil ca o clasa sa fie mostenita indirect
de mai multe ori, prin intermediul unor clase care mostenesc, fiecare in
parte, clasa de baza. De exemplu:
class L A public: int x;S; class A : public L A /* */S; class B : public L A /* */S; class D : public A, public B A /* */S;
Acesta mostenire se poate reprezenta printr-un graf aciclic directionat (directed
acyclic graph -; DAG) care indica relatiile dintre subobiectele unui obiect
din clasa D. Din graful de reprezentare a mostenirilor, se poate observa faptul
ca L este replicata in clasa D.
Un obiect din clasa D va contine membrii clasei L de doua ori, o data prin clasa
A (A::L) si o data prin clasa B (B::L). Se pot reprezenta partile distincte
ale unui astfel de obiect:
In aceasta situatie, acesul la un membru al clasei L (de exemplu variabila
x), al unui obiect de tip D este ambiguu si deci interzis (este semnalat ca
eroare la compilare):
D ob; ob.x = 2; // eroare D::x este ambiguu; poate fi in
// baza L a clasei A sau in baza L a clasei B
Se pot elimina amiguitatile si deci erorile de compilare prin calificarea variabilei
cu domeniul clasei careia ii apartine:
ob.A::x = 2; // corect, x din A ob.B::x = 3; // corect, x din B
O alta solutie pentru eliminarea ambiguitatilor in mostenirile multiple
este de impune crearea unei singure copii din clasa de baza in clasa derivata.
Pentru aceasta este necesar ca acea clasa care ar putea produce copii multiple
prin mostenire indirecta (clasa L, in exemplul de mai sus) sa fie declarata
clasa de baza de tip virtual in clasele care o introduc in clasa
cu mostenire multipa. De exemplu:
class L A public: int x; S; class A : virtual public L A /* */ S; class B : virtual public L A /* */ S; class D : public A, public B A /* */S;
O clasa de baza virtuala este mostenita o singura data si creaza o singura
copie in clasa derivata. Graful de reprezentare a mostenirilor din aceste
declaratii ilustreaza acest comportament:
O clasa poate avea atat clase de baza virtuale cat si nevirtuale,
chiar de acelasi tip. De exemplu, in declaratiile:
class L A public: int x; S; class A : virtual public L A /* */ S; class B : virtual public L A /* */ S; class C : public L A /* */ S; class D : public A, public B, public C A /* */ S;
clasa D mosteneste indirect clasa L: de doua ori ca o clasa de baza virtuala
prin mostenirea din clasele A si B si inca o data direct, prin mostenirea
clasei C. Reprezentarea grafica a unei astfel de mosteniri este urmatoarea:
Un obiect din clasa D va contine doua copii a clase L: o singura copie prin
mostenirea virtual prin intermediul claselor A si B si o alta copie prin mostenirea
clasei C. Ambiguitatile care pot sa apara in astfel de situatii se rezolva
prin calificarea membrilor cu numele clasei (domeniului) din care fac parte.
5.4 Functii virtuale si polimorfism
O functie virtuala este o functie care este declarata de tip virtual in
clasa de baza si redefinita intr-o clasa derivata. Redefinirea unei functii
virtuale intr-o clasa derivata domina (override) definitia functiei in
clasa de baza. Functia declarata virtual in clasa de baza actioneaza ca
o descriere generica prin care se defineste interfata comuna, iar functiile
redefinite in clasele derivate precizeaza actiunile specifice fiecarei
clase derivate.
Mecanismul de virtualitate asigura selectia (dominarea) functiei redefinite
in clasa derivata numai la apelul functiei pentru un obiect cunoscut printr-un
pointer. In apelul ca functie membra a unui obiect dat cu numele lui,
functiile virtuale se comporta normal, ca functii redefinite.
Deoarece mecanismul de virtualitate se manifesta numai in cazul apelului
functiilor prin intermediul pointerilor se vor preciza mai intai
aspectele privind conversiile de pointeri intre clasele de baza si clasele
derivate.
5.4.1 Conversia pointerilor intre clase de baza si derivate
Conversia unui pointer la o clasa derivata in pointer la o clasa de baza
a acesteia este implicita, daca derivarea este de tip public si nu exista ambiguitati.
Rezultatul conversiei este un pointer la subobiectul din clasa de baza al obiectului
din clasa derivata. Acest lucru este posibil deoarece un obiect dintr-o clasa
derivata contine cate un pointer ascuns la fiecare din subobiectele componente
(care sunt din obiecte din clasele de baza).
Conversia inversa, a unui pointer la o clasa de baza in pointer la derivata
nu este admisa implicit. O astfel de conversie se poate forta explicit prin
operatorul de conversie cast. Rezultatul unei astfel de conversii este insa
nedeterminat, de cele mai multe ori provocand erori de executie. Se vor
explicita mai clar aceste aspecte prin urmatorul exemplu.
? Exemplul 5.4
#include <iostream.h> class B A /* */ S; class D:public B A /* */ S; void main()A
D d;
B* pb = &d; // corect, conversie implicita
B ob;
D* pd = &ob; // eroare la compilare, nu se
// poate converti implicit de la
// class B* la class D1*
D* pd =(D*)&ob; // se compileaza corect, dar
// rezultatul este nedeterminat
S
S-a definit o clasa de baza B si o clasa derivata D. Conversia pointerului
de tip D* (la un obiect din clasa derivata) in pointer de tip B* (la subobiectul
component din clasa de baza) este legala si implicita. Conversia inversa nu
este admisa implicit, iar fortarea prin operatorul de conversie cast este admisa
de compilator dar produce rezultate nedeterminate la executie. ?
Se mai poate adauga faptul ca si referintele pot fi convertite in mod
asemanator: o referinta la o clasa poate fi convertita implicit intr-o
referinta la o clasa de baza a acesteia daca derivarea este de tip public si
nu exista ambiguitati. Rezultatul conversiei este o referinta la subobiectul
component al obiectului de tip derivat.
Referintele nu pot fi utilizate in mecanismul de virtualitate deoarece
ele trebuie sa fie intializate la conversie.
5.4.2 Functii virtuale
Atunci cand o functie normala (care nu este virtuala) este definita intr-o
clasa de baza si redefinita in clasele derivate, la apelul acesteia ca
functie membra a unui obiect pentru care se cunoaste un pointer, se selecteaza
functia dupa tipul pointerului, indiferent de tipul obiectului al carui pointer
se foloseste (obiect din clasa de baza sau obiect din clasa derivata).
Daca o functie este definita ca functie virtuala in clasa de baza si redefinita
in clasele derivate, la apelul acesteia ca functie membra a unui obiect
pentru care se cunoaste un pointer, se selecteaza functia dupa tipul obiectului,
nu al pointerului. Sunt posibile mai multe situatii:
• Daca obiectul este din clasa de baza nu se poate folosi un pointer
la o clasa derivata (Exemplul 5.4).
• Daca obiectul este de tip clasa derivata si pointerul este pointer la
clasa derivata, se selecteaza functia redefinita in clasa derivata respectiva.
• Daca obiectul este de tip clasa derivata, iar pointerul folosit este
un pointer la o clasa de baza a acesteia, se selecteaza functia redefinita in
clasa derivata corespunzatoare tipului obiectului.
Acesta este mecanismul de virtualitate si el permite implementarea polimorfismului
in clasele derivate. O functie redefinita intr-o clasa derivata
domina functia virtuala corespunzatoare din clasa de baza si o inlocuieste
chiar daca tipul pointerului cu care este accesata este pointer la clasa de
baza.
Pentru precizarea acestui comportament al functiilor se analizeaza mai multe
situatii in exemplul urmator.
? Exemplul 5.5
Se considera o clasa de baza B si doua clase derivate D1 si D2. In clasa
de baza sunt definite doua functii: functia normala f()si functia virtuala g().
In fiecare din clasele derivate se redefinesc cele doua functii f() si
g(). In functia main() se creaza trei obiecte: un obiect din clasa de
baza B indicat prin pointerul B* pb si doua obiecte din clasele derivate D1
si D2. Fiecare dintre obiectele derivate poate fi indicat printr-un pointer
la clasa derivata respectiva (D1* pd1, respectiv D2* pd2), precum si printr-un
pointer la baza corespunzator (B* pb1 = pd1, respectiv B* pb1 = pd1).
Mesajele afisate la consola la apelul functiilor f() si g()pentru obiecte indicate
prin pointeri de tipuri diferite evidentiaza diferenta de comportare a unei
functii virtuale fata de o functie normala.
#include <iostream.h> class B A public: void f() A cout << "f() din B\n"; S virtual void g()A cout << "g() din B\n"; S
S; class D1:public B A public: void f() A cout << "f() din D1\n"; S void g() A cout << "g() din D1\n"; S
S; class D2:public B A public: void f() A cout << "f() din D2\n"; S void g() A cout << "g() din D2\n"; S
S; void fv1 A
B* pb = new B;
D1* pd1 = new D1;
D2* pd2 = new D2;
B* pb1 = pd1;
B* pb2 = pd2;
// f() este functie normala
// g() este functie virtuala
// Obiect B, pointer B* pb->f(); // f() din B pb->g(); // g() din B
// Obiecte D1, D2 , pointeri D1*, D2* pd1->f(); // f() din D1 pd2->f(); // f() din D2 pd1->g(); // g() din D1 pd2->g(); // g() din D2
// Obiecte D1, D2 , pointeri B*, B* pb1->f(); // f() din B pb2->f(); // f() din B pb1->g(); // g() din D1 pb2->g(); // g() din D2
delete pb; delete pd1; delete pd2;
S
Mesajele care se afiseaza la consola la executia functiei fv1() sunt cele inscrise
ca si comentarii in program.
In primele situatii, cand pointerul este pointer la tipul obiectului,
nu se manifesta nici o deosebire intre comportarea unei functii virtuale
fata de comportarea unei functii normale: se selecteaza functia corespunzatoare
tipului pointerului si obiectului.
Diferenta de comportare se manifesta in ultima situatie, atunci cand
este apelata o functie pentru un obiect de tip clasa derivata printr-un pointer
la o clasa de baza a acestuia. Pentru functia normala f() se selecteaza varianta
depinzand de tipul pointerului. Pentru functia virtuala g() se selecteaza
varianta in functie de tipul obiectului, chiar daca este accesat prin
pointer de tip baza.
?
Polimorfismul, adica apelul unei functii dintr-o clasa derivata, este posibil
numai prin utilizarea pointerilor la obiecte. Obietele insele determina
univoc varianta functiei apelate, deci nu se pot selecta alte functii decat
cele ale obiectului de tipul respectiv. De exemplu, pentru aceleasi clase definite
ca mai sus, se considera functia fv2():
void fv2()A
B obB;
D1 obD1;
D2 obD2; obB.f(); // f() din B obB.g(); // g() din B obD1.f(); // f() din D1 obD1.g(); // g() din D1 obD2.f(); // f() din D2 obD2.g(); // g() din D2
S
Comentariile arata care dintre functiile redefinite este utilizata, si anume
functia care corespunde tipilui obiectului, indiferent daca este virtuala sau
nu.
In acest moment se pot preciza mai detaliat asemanarile si diferentele
dintre functiile supraincarcate (overloaded), functiile redefinite (redefined)
si functiile virtuale. In toate aceste situatii numele functiilor este
acelasi.
In cazul functiilor supraincarcate lista de argumente trebuie sa
difere astfel incat selectia unei variante a functiei sa poata fi
facuta fara ambiguitate dupa tipul argumentelor de apel.
Functiile redefinite sau virtuale trebuie sa aiba aceleasi argumente de apel
(acelasi protiotip).
Dintre mai multe functii (o functie definita de tip virtual in clasa de
baza si redefinita in clasele derivate), selectia variantei functiei se
face dupa tipul obiectului pentru care este invocata.
Dintre mai multe functii (o functie definita fara specificatorul virtual in
clasa de baza si redefinita in clasele derivate), selectia variantei functiei
se face dupa tipul pointerului cu care este invocata.
Nu pot fi declarate functii virtuale: functii ne-membre, constructori, functii
de tip static, functii friend.
5.4.3 Mostenirea functiilor virtuale
Atributul virtual se mosteneste pe tot parcursul derivarii. Cu alte cuvinte,
daca o functie este declarata de tip virtual in clasa de baza, functia
redefinita intr-o clasa derivata este functie de tip virtual in
mod implicit (fara sa fie nevoie de declaratie explicita virtual). Acest lucru
inseamna ca, daca aceasta clasa derivata serveste pentru definirea unei
noi derivari, functia va fi mostenita de asemenea de tip virtual.
Un exemplu in acest sens poate fi obtinut usor din clasele definite in
Exemplul 5.5 (clasa B si clasa D1) la care se mai adauga inca o clasa
derivata astfel:
class DD1:public D1A public: void f() A cout << "f() din DD1\n";S void g() A cout << "g() din DD1\n";S
S;
Mesajele care se obtin la executia functiei urmatoare sunt prezentate ca si
comentarii:
void fdd()A
DD1* pdd1 = new DD1();
B* pb = pdd1;
D1* pd1 = pdd1; pb->f(); // f() din B pd1->f(); // f() din D1 pdd1->f(); // f() din DD1
pb->g(); // g() din DD1 pd1->g(); // g() din DD1 pdd1->g(); // g() din DD1
S
S-a creat un obiect de tip clasa derivata DD1 in memoria heap folosind
operatorul de alocare dinamica new. Acest obiect poate fi indicat prin trei
pointeri: pointerul la clasa derivata DD1 (DD1* pdd1), returnat de operatorul
new, un pointer la clasa de baza B (B* pb) si un pointer la clasa de baza D1
(D1* pd1) . Ultimii doi pointeri au fost obtinut prin conversie implicita de
la primul pointer.
Daca se apeleaza functiile f() si g() pentru obiectul creat, cu fiecare dintre
cei trei pointeri, se poate observa ca functia g() se comporta intotdeauna
ca functie virtuala, chiar daca nu a fost folosit specificatorul virtual la
redefinire in clasele derivate D1 si DD1.
Pot sa apara si alte situatii in mostenirea functiilor virtuale. De exemplu,
daca o functie virtuala nu este redefinita intr-o clasa derivata, atunci
se foloseste functia mostenita din clasa de baza si pentru obiecte de tip clasa
derivata. Pentru precizarea acestei situatii se reiau clasele B, D1 si D2 din
Exemplul 5.5, modificate astfel:
#include <iostream.h> class B A public: virtual void g()A cout << "g() din B\n"; S
S; class D1:public B A public: void g() A cout << "g() din D1\n"; S
S; class D2:public B A S;
void main() A
D1* pd1 = new D1();
D2* pd2 = new D2(); pd1->g(); // g() din D1 pd2->g(); // g() din B
S
Functia virtuala g()a fost redefinita in clasa D1 si apoi a fost apelata
pentru un obiect de clasa D1. In clasa D2 functia virtuala g() nu a fost
redefinita, si de aceea la invocarea pentru un obiect din clasa D2 este folosita
functia g() din clasa de baza.
5.4.4 Destructori virtuali
Destructorii pot fi declarati de tip virtual, si aceasta declaratie este necesara
in situatia in care se dezaloca un obiect de tip clasa derivata
folosind pointer la o clasa de baza a acesteia. De exemplu:
#include <iostream.h> class BA public:
B()A cout << "Constructor B\n";S
IB()Acout << "Destructor B\n";S
S; class D:public B A public:
D()A cout << "Constructor D\n";S
ID()Acout << "Destructor D\n";S
S; void main()A
B* p = new D(); // creaza un obiect D delete p; // sterge un obiect B
S
Eroarea care apare la executia acestui program este aceea ca a fost creat un
obiect de clasa D si a fost sters numai un subobiect al acestuia, subobiectul
din clasa de baza B. Mesajele afisate evidentiaza acest lucru:
Constructor B
Constructor D
Destructor B
Acest lucru face ca memoria libera (heap) sa ramana ocupata in
mod inutil (cu partea de date a clasei D), desi se intentiona eliminarea completa
din memorie a obiectului creat. Declararea destructorului din clasa de baza
de tip virtual rezolva aceasta problema:
class BA public:
B()A cout << "Constructor B\n";S virtual IB()Acout << "Destructor B virtual\n";S
S;
Fara sa fie modificata clasa D sau functia main()de mai sus, la executia acesteia
dupa declararea destructorului clasei B ca virtual, se obtin mesajele:
Constructor B
Constructor D
Destructor D
Destructor B virtual
care indica faptul ca, la executia instructiunii delete p a fost apelat desctructorul
clasei derivate D, dupa tipul obiectului (obiectul este de clasa D) nu al pointerului
folosit (p este de tip B*). Destructorul D::ID() elibereaza partea din clasa
derivata a obiectului, dupa care (in mod implicit, fara sa fie nevoie
ca programatorul sa specifice acest lucru), este apelat destructorul clasei
de baza B care elibereaza subobiectul de baza din obiectul dat.
5.4.5 Clase abstracte
De cele mai multe ori functia declarata de tip virtual in clasa de baza
nu defineste o actiune semnificativa si este neaparat necesar ca ea sa fie redefinita
in fiecare din clasele derivate. Pentru ca programatorul sa fie obligat
sa redefineasca o functie virtuala in toate clasele derivate in
care este folosita aceasta functie, se declara functia respectiva virtuala pura.
O functie virtuala pura este o functie care nu are definitie in clasa
de baza, iar declaratia ei arata in felul urmator: virtual tip_returnat nume_functie(lista_arg) = 0;
O clasa care contine cel putin o functie virtuala pura se numeste clasa abstracta.
Deoarece o clasa abstracta contine una sau mai multe functii pentru care nu
exista definitii (functii virtuale pure), nu pot fi create instante (obiecte)
din acea clasa, dar pot fi creati pointeri si referinte la astfel de clase abstracte.
O clasa abstracta este folosita in general ca o clasa fundamentala, din
care se construiesc alte clase prin derivare.
Orice clasa derivata dintr-o clasa abstracta este, la randul ei clasa
abstracta (si deci nu se pot crea instante ale acesteia) daca nu redefinesc
toate functiile virtuale pure mostenite.
Daca o clasa redefineste toate functiile virtuale pure ale claselor ei de baza,
devine clasa normala (ne-abstracta) si pot fi create instante (obiecte) ale
acesteia.
Exemplul urmator (5.6) evidentiaza caracteristicile si restrictiile legate de
clasele abstracte si functiile virtuale pure.
? Exemplul 5.6
#include <iostream.h> class X A // clasa abstracta public: virtual void fp()=0;// functie virtuala pura
S; class Y : public X A S; // clasa abstracta class Z : public YA // clasa normala (ne-abstracta) public: void fp()Acout<< "f() din Z\n";S // redefinire fp()
S;
void main ()A
X obx; // eroare: nu se poate instantia
// clasa abstracta X
Y oby; // eroare: nu se poate instantia
// clasa abstracta Y
Z obz; // corect, clasa Z nu este abstracta
X* pxz = &obz; // corect, se pot defini pointeri
// la clasa abstracta
Y* pyz = &obz; // corect, la fel ca mai sus pxz->fp(); // corect, fp() din Z pyz->fp(); // corect, fp() din Z obz.fp(); // corect, fp() din Z
S
Din clasa abstracta X a fost derivata clasa, de asemenea abstracta, Y. Numai
clasa Z redefineste functia virtuala pura fp(), deci ea poate fi instantiata.
Comentariile scrise sunt suficiente pentru a fi inteleasa comportarea
in continuare a acestor clase. ?
Un exemplu mai complet care evidentiaza utilitatea functiilor virtuale in
general si a functiilor virtuale pure in special este Exemplu 5.7 de mai
jos.
? Exemplul 5.7
#include <iostream.h> class ConvertA protected: double x; // valoare intrare double y; // valoare iesire public:
Convert(double i)Ax = i;S double getx()Areturn x;S double gety()Areturn y;S virtual void conv() = 0;
S;
// clasa FC de conversie grade Farenheit in grade Celsius class FC: public ConvertA public:
FC(double i):Convert(i)AS void conv()Ay = (x-32)/1.8;S
S;
// clase IC de conversie inch in centimetri class IC: public ConvertA public:
IC(double i):Convert(i)AS void conv()Ay = 2.54*x;S
S; void main ()A
Convert* p = 0; // pointer la baza cout<<"Introduceti valoarea si tipul conversiei: "; double v; char ch; cin >> v >> ch; switch (ch)A case 'i': //conversie inch --> cm (clasa IC) p = new IC(v); break; case 'f': //conv. Farenheit --> Celsius (clasa FC) p = new FC(v); break;
S if (p)A p->conv(); cout <<p->getx()<< "---> "<<p->gety()<<
endl; delete p;
S
S
Acest exemplu este un mic program de conversie a unor date dintr-o valoare
de intrare intr-o valoare de iesire; de exemplu, din grade Farenheit in
grade Celsius, din inch in centimetri, etc.
Clasa de baza absrtracta Convert este folosita pentru crearea prin derivare
a cate unei clase specifice fiecarui tip de conversie de date dorit. Aceasta
clasa defineste datele comune, necesare oricarui tip de conversie preconizat,
de la o valoare de intrare x la o valoare de iesire y. Functia de conversie
conv() nu se poate defini in clasa de baza, ea fiind specifica fiecarui
tip de conversie in parte; de aceea functia conv() se declara functie
virtuala pura si trebuie sa fie redefinita in fiecare clasa derivata.
In functia main() se executa o conversie a unei valori introduse de la
consola, folosind un tip de conversie (o clasa derivata) care se selecteaza
pe baza unui caracter introdus la consola.
Acesta este un exemplu in care este destul de pregnanta necesitatea functiilor
virtuale: deoarece nu se cunoaste in momentul compilarii tipul de conversie
care se va efectua, se foloseste un pointer la clasa de baza pentru orice operatie
(crearea unui obiect de conversie nou, apelul functiei conv(), afisarea rezultatelor,
distrugerea obiectului la terminarea programului). Singura diferentiere care
permite selectia corecta a functiilor, este tipul obiectului creat, care depinde
de tipul conversiei cerute la consola.
Se propune ca exercitiu implementarea acestui program de conversie fara folosirea
functiilor virtuale.
5.4.6 Polimorfismul
Dupa cum s-a mentionat, una din caracteristicile importante ale programarii
orientate pe obiecte este aceea ca permite definirea unei interfete comune pentru
mai multe metode specifice diferitelor functionalitati. Aceasta comportare,
care asigura simplificarea si organizarea sistemelor de progrme complexe, este
cunosuta sub numele de polimorfism. Polimorfismul introdus prin mecanismul de
virtualitate este polimorfism la nivel de executie, care permite legarea tarzie
(late binding) intre evenimentele din program, in contrast cu legarea
timpurie (early binding), proprie apelurilor functiilor normale (nevirtuale).
Intercorelarea (legarea) timpurie se refera la evenimentele care se desfasoarai
in timpul compilarii si anume apeluri de functii pentru care sunt cunoscute
adresele de apel: functii normale, functii supraincarcate, operatori supraincarcati,
functii membre neviruale, functii friend. Apelurile rezolvate in timpul
compilarii beneficiaza de o eficienta ridicata.
Termenul de legare tarzie se refera la evenimente din timpul executiei.
In astfel de apeluri, adresa functiei care urmeaza sa fie apelata nu este
cunoscuta decat in momentul executiei programului. Functiile virtuale
sunt evenimente cu legare tarzie: accesul la astfel de functii se face
prin pointer la clasa de baza, iar apelarea efectiva a functiei este determinata
in timpul executiei de tipul obiectului indicat, pentru care este cunoscut
pointerul la clasa sa de baza.
Avantajul principal al legarii tarzii (permisa de polimorfismul asigurat
de functiile virtuale) il constitue simplitatea si flexibilitatea programelor
rezultate. Bineinteles, exista si dezavantaje, cel mai important fiind
timpul suplimentar necesar selectiei functiei apelate efectiv din timpul executiei,
ceea ce conduce la un timp de executie mai mare al programelor. Cu alte cuvinte,
folosirea functiilor virtuale trebuie rezervata numai pentru situatii in
care sunt in mod real necesare, atunci cand beneficiul adus de polimorfism
depaseste costul suplimentar de executie.
5.5 Implementarea colectiilor de date prin clase derivate
Asa cum s-a prezentat in sectiunea 3, o colectie de date se caracterizeaza
prin modelul de date pe care il reprezinta, care da forma colectiei (de
exemplu, lista, arbore, multime) si prin tipul datelor (obiecte ale colectiei),
care pot fi de tip predefinit sau definit de utilizator. Definirea individuala
a claselor de reprezentare a unei colectii de o anumita forma pentru fiecare
tip de obiecte conduce la un numar imens de reprezentari, toate avand
aceeasi organizare si functionalitate si diferind doar prin tipul de date la
care se refera. S-a putut remarca acest lucru din definirea repetata a claselor
pentru reprezentarea tablourilor de diferite tipuri (tablouri de numere intregi,
tablouri de siruri de caractere, etc) sau a listelor inlantuite. Implementarea
mai generala a unor clase de colectii prin folosirea pointerilor generici ca
informatii in tablouri, liste sau arbori implica numeroase restrictii
in definirea si utilizarea acestora si in general sunt considerate
nesigure ca tip, datorita conversiilor explicite (cast) a tipurilor de pointeri,
si deci necontrolate de catre compilator.
O modalitate de a implementa clase de colectii “sigure ca tip” (type-safe)
pentru diferite tipuri de date definite de utilizator (clase) este de defini
o clasa de baza din care se deriveaza toate clasele pentru care urmeaza sa fie
definite colectii de obiecte. Forma colectiei (lista, vector, etc.) se defineste
pentru clasa de baza, iar pentru o colectie de aceeasi forma pentru un tip specific
de date se poate folosi direct colectia pentru clasa de baza sau se poate deriva
o clasa de colectie proprie prin redefinirea unui numar redus de functii. Se
poate intelege mai usor acest mecanism pe un exemplu de implementare a
tablourilor de obiecte de tipuri definite de utilizator.
5.1.2 Implementarea unui tablou de obiecte
Pentru imnplementarea unui tablou de obiecte de diferite tipuri definite de
utilizator se defineste clasa Object ca o clasa de baza pentru toate tipurile
derivate:
class ObjectA public:
Object() AS;
IObject() AS;
S;
Clasa ObArray, derivata din clasa Object, defineste un vector de pointeri de
tip Object* . Nu este necesar sa fie limitata dimensiunea vectorului deoarece
se asigura cresterea dimensiunii acestuia atunci cand este necesar.
class ObArray : public Object A
Object **p; // vector de pointeri int size; // numar de elemente int grows; // increment de crestere dimens. int dimens; // dimens. vector public:
ObArray() A size = 0; grows = 4; dimens = grows; p=(Object**)new BYTEagrows*sizeof(Object*)i;
S void RemoveAll(); int GetSize() A return size; S int Add(Object* x); int InsertAt(int i, Object *x); int RemoveAt(int i);
Object* GetAt(int i) A if(i>=0 && i<size) return paii; else return 0;
S
S;
Constructorul clasei ObArray construieste un vector de pointeri de dimensiune
(size) zero.
Elemente noi (pointeri de tip Object*) se pot adauga la sfarsitul vectorului,
folosind functia Add(), sau se pot insera intr-o pozitie dorita (functia
InsertAt()), si de fiecare data dimensiunea vectorului creste daca este necesar:
int ObArray::Add(Object *x)A if(size<dimens) pasize++i=x; else A
Object **p1 = p; dimens = size + grows; p=(Object**)new BYTEadimens*sizeof(Object*)i; for(int i=0;i<size;i++) paii = p1aii; delete ai (BYTE *)p1; pasize++i = x;
S return size-1;
S int ObArray::InsertAt(int i, Object *x)A if(i<0 || i>size) return 0; if(i==size) return Add(x); if(size<dimens) A for(int j=size;j>i;j--) paji=paj-1i; paii=x;
S else A
Object **p1 = p; dimens = size + grows; p=(Object**)new BYTEadimens*sizeof(Object*)i; for(int j=0;j<i;j++) paji = p1aji; paii=x; for(j=i;j<size;j++) paj+1i=p1aji; delete p1;
S size++; return 1;
S
Functiile membre RemoveAt() si RemoveALL() elimina un pointer dintr-o pozitie
data, fara sa stearga obiectul indicat de acesta, sau elimina intreg vectorul
de pointeri, transformandu-l intr-un vector de dimensiune nula:
int ObArray::RemoveAt(int i) A if(i<0 || i>=size) return 0; for(int j=i;j<size-1;j++) paji=paj+1i; size--; return 1;
S void RemoveAll()A delete ai (BYTE *)p; p = 0; size = 0;
S
Celelate functii ale clasei sunt usor de inteles.
Pentru o colectie de obiecte de o clasa data, se foloseste clasa Object ca
si clasa de baza a acesteia. De exemplu, pentru reprezentarea unui vector de
puncte in plan, clasa Point (definita in sectiunea 2) se modifica
astfel incat sa aiba ca si clasa de baza clasa Object:
class Point : public Object A double x,y; public:
Point(double xx, double yy) Ax=xx; y=yy;S
Point(double v)A x = v; y = v;S
IPoint() AS; friend ostream &operator<<(ostream &stream,
Point &p) A return stream<<"X= "<<p.x<<" "<<"Y=
"<<p.y;
S
//………………
S;
Pentru definirea unui vector de puncte, o prima modalitate este aceea de a
crea o clasa noua, clasa PointArray derivata din clasa colectiei de baza, ObArray,
in felul urmator:
class PointArray : public ObArray A public:
PointArray() AS;
IPointArray();
Point *GetAt(int i)A return (Point*)ObArray::GetAt(i); S
S;
PointArray::IPointArray()A int size = GetSize(); for(int i=0;i<size;i++) A
Point *p = GetAt(i); delete p;
S
RemoveAll();
S
In clasa PointArray este necesar sa fie redefinite un numar redus de functii,
majoritatea fiind folosite cele mostenite de la clasa de baza ObArray. In
primul rand trebuie sa fie definit destructorul (care nu se mosteneste)
si apoi toate functiile care returneaza un pointer de tipul specific (Point*),
deoarece conversia de la tipul de baza la tipul derivat nu este admisa implicit
((Point*) ObArray::GetAt(i); ).
In schimb, dat fiind ca exista o conversie implicita de la pointer de
tip clasa derivata (Point*) la pointer la clasa de baza (Object*), nu sunt necesare
redefiniri ale functiilor de forma:
int PointArray::Add(Point *x)A return ObArray::Add((Object*)x);S
deoarece, daca nu este redefinita functia Add() in clasa derivata PointArray,
atunci oricum se foloseste functia Add() din clasa de baza ObArray, iar conversia
unui pointer de tipul Point* in pointer la clasa de baza (Object*) este,
de asemenea implicita. Exemplu de utilizare a clasei
void fpa1()A
PointArray array; for(int i=0;i<5;i++) A
Point *p = new Point(i); array.Add(p);
S array.RemoveAt(2); array.InsertAt(3,new Point(9.0));
int size = array.GetSize(); for(i=0;i<size;i++) A
Point *p = array.GetAt(i); cout << *p <<endl;
S
S
La executia acestei functii se afiseaza urmatoarele mesaje:
X = 0 Y= 0
X = 1 Y = 1
X = 2 Y = 2
X = 9 Y = 9
X = 4 Y = 4
O a doua modalitate de a defini o colectie de forma vector pentru tipul de
date Point, este aceea de a folosi direct clasa colectiei de baza, ObArray,
cu specificarea de fiecare data a conersiei de tip necesare. De exemplu, daca
se rescrie functia fpa1(), in care se declara : ObArray array; se obtine
o eroare de compilare datorita faptului ca nu poate fi convertit implicit pointerul
de tip Object* returnat de functia Add() a clasei ObArray in pointer de
tipul Point*. Solutia este simpla, de a defini o conversie explicita astfel:
void fpa1()A
ObArray array; for(int i=0;i<5;i++) A
Point *p = new Point(i); array.Add(p);
S array.RemoveAt(2); array.InsertAt(3,new Point(9.0));
int size = array.GetSize(); for(i=0;i<size;i++) A
Point p = (Point*)array.GetAt(i); cout << *p <<endl;
S
S
La executia acestei functii se obtin aceleasi rezultate ca si la executia functiei
precedente.
Astfel de implementari ale claselor de colectii sigure se gasesc in bibliotecile
C++. De exemplu in biblioteca Microsoft Foundation Class, care este bibiloteca
de clase a compilatorului Microsoft Visual C++ sunt prevazute mai multe clase
de colectii sigure ca tip. Clasa CObArray reprezinta un vector de pointeri la
obiecte din clasa CObject (clasa Cobject este folosita ca o clasa de baza pentru
majoritatea claselor MFC). Clasa CObList reprezinta o lista dublu inlantuita
de pointeri neunici la obiecte de clasa CObject. Alte variante de clase de colectii
din MFC se pot gasi in manualul de referinta.
5.6 Implementarea colectiilor de date prin clase container
Implementarile colectiilor de date prezentate in aceasta sectiune, ca
si in sectiunea 3, folosesc clase concrete, in sensul ca toate operatiile
acestora sunt definite si ele pot fi folosite pentru instantieri. Clasele concrete
(tipurile concrete -; concrete data types) permit definirea fiecarui concept
care nu are suport predefinit in limbaj, dar nu exprima partile comune
ale mai multor implementari ale unui anumit concept (model de date). De exemplu,
o multime poate fi implementata printr-o lista sau printr-un vector, dar clasele
concrete care reprezinta aceste structuri nu pot exprima conceptul de multime
comun celor doua modalitati de implementare.
O metoda de a crea programe in care se pot exprima partea comuna (conceptul)
mai multor implementari este de a introduce o clasa de baza abstracta care reprezinta
interfata unei multimi de implementari a unui concept comun. O astfel de clasa
abstracta folosita pentru definirea colectiilor de date se numeste clasa container.
Se considera o multime de obiecte de un tip oarecare T, pentru care se defineste
o interfata printr-un iterator, folosind clasa abstracta Set:
class Set A public: virtual void insert(T*) = 0; virtual void remove(T*) = 0; virtual int lookup(T*) = 0;
// iterator virtual T* first()= 0; virtual T* next() = 0; virtual ISet() AS;
S;
Absenta constructorului si prezenta destructorului virtual este tipica in
clasele abstracte. Toate functiile membre (cu exceptia destructorului) sunt
functii virtuale pure. Fie clasele TList, respectiv TArray, clasele care definesc
o lista, respectiv un vector de obiecte de tip T. Folosind aceste clase se pot
defini mai multe implementari ale unei multimi. De exemplu:
class TArraySet : public Set, private TArray A int index; public: void insert(T*); void remove(T*); int lookup(T*);
T* first() Aindex = 0; return next();S
T* next();
TArraySet(int size):
TArray(size), index(0) AS
S;
Clasa TArraySet foloseste tipul concret de implementare al multimii ca vector
(clasa concreta TArray) ca o clasa de baza cu tip de acces private si redefineste
functiile virtuale ale clasei Set, asigurandu-se astfel o interfata identica
cu a oricarei alte implementari care foloseste clasa Set ca si clasa de baza.
In mod asemanator se poate defini o clasa TListSet, avand ca si
clase de baza clasa abstracta Set si clasa concreta TList, care implementeaza
o lista inlantuita. Clasa TListSet defineste o multime reprezentata ca
lista inlantuita si, bineinteles, redefineste toate functiile virtuale
ale clase Set.
Cele doua clase TListSet si TArraySet implementeaza fiecare modelul de date
(conceptul) multime reprezentat ca lista, respectiv ca vector, dar avand
interfata identica, asigurata prin mostenirea clasei abstracte Set, care exprima,
prin interfata pe care o pune la dispozitie, acest concept de multime.
Mai mult, instante ale claselor TListSet si TArraySet (deci multimi, reprezentate
insa diferit, ca lista sau ca vector) pot fi prelucrate prin aceleasi
functii care folosesc pointeri la clasa de baza Set:
void fproc(Set* s) A for (T* p = s.first(); p = s.next())A
// prelucrare
S
S void fabstr()A
TArraySet set1(1000); // vector de 1000 de elemente
TListSet set2; fproc(&set1); fproc(&set2);
S
Asadar, clasele container folosite pentru implementarea colectiilor de date
sunt clase abstracte care asigura definirea unui concept intr-un mod care
permite coexistenta intr-un program a mai multor implementari ale acestuia.
Clasele abstracte se pot utiliza in acelasi mod pentru definirea oricarui
concept, nu numai pentru concepte privitoare la colectiile de date, asa cum
a fost prezentat in aceasta subsectiune.