Sunteți pe pagina 1din 22

Cap.

7
Mecanismul motenirii. Construirea ierarhiilor de clase
Motenirea (inheritance) reprezint un mecanism sau o tehnic de programare prin care se pot reutiliza
i extinde clasele existente; avantajul const n faptul c se poate extinde codul scris, fr a mai fi
necesar rescrierea celui original. n acest capitol se vor aborda urmtoarele teme de interes:
specificatorii de acces, utilizarea membrilor de tip protected, funciile constructor, destructor i
mecanismul motenirii, mecanismul motenirii multiple, prezentarea claselor de baz virtuale.
Motenirea reprezint elementul distinctiv al paradigmei programrii orientate pe obiecte. n C++,
mecanismul de motenire se realizeaz prin declararea unei (unor) clase de baz din care se deriv
apoi o alt clas (alte clase) numit (numite) clas (clase) derivat (derivate). Clasele derivate
motenesc structurile de date i funciile clasei de baz. n plus, n clasele derivate pot fi adugate
noi funcii membre, iar membrii motenii pot fi modificai. De exemplu, o clas A care motenete
membrii unei alte clase B, se spune c este derivat din clasa B, iar clasa A este o clas de baz pentru
B.
La rndul ei, o clas derivat poate fi folosit drept clas de baz pentru a deriva din ea alte clase. Prin
urmare se pot construi ierarhii de clase, unde fiecare clas servete drept printe sau rdcin
pentru o nou clas.
Observaie: O clas poate moteni mai multe clase, caz n care este vorba despre o motenire
multipl, n caz contrar fiind vorba despre o motenire simpl.
7.1. Derivarea claselor. Specificatorii de acces
Sintaxa pentru declararea unei clase derivate dintr-o clas de baz este urmtoarea:
class nume_clasa_derivata : specificator_de_acces nume_clasa_de_baza {
// Implementarea clasei
};
unde specificator_de_acces poate fi unul din cuvintele cheie: public, private sau protected.
Specificatorul de acces determin modul n care elementele clasei de baz sunt motenite de clasa
derivat i poate modifica dreptul de acces al acestora aa cum este prezentat n tabeul urmtor:
Tipul de acces al unui
membru n clasa de baz
Public
Private
Protected
Public
Private
Protected

Tipul de motenire
Public
Private

Tipul de acces al membrului dup


motenire (n clasa derivat)
Public
Inaccesibil
Protected
Private
Inaccesibil
Private

Dac specificatorul de acces lipsete, implicit acesta va fi private pentru class i public pentru
struct.
Exemplele care urmeaz evideniaz aceste aspecte.
Observaie: Uniunile nu pot face parte dintr-o ierarhie de mai multe clase, deoarece ele nu pot fi nici
clase de baz i nici clase derivate.
Exemplul 7.1. O structur derivat dintr-o clas de tip struct
struct Punct {
double x,y;
void SetCoord(double a, double b) { x = a; y = b; }
};
struct Cerc: Punct {
7-1

double r;
void SetRaza(double a) { r = a; }
};
Un obiect dintr-o clas derivat este privit de ctre compilator ca avnd toi membrii specificai n
clasa derivat, la care se adaug un obiect ascuns care este o instan a clasei de baz. n acest mod
motenirea reprezint o relaie de compunere special. Pentru exemplul precedent, un obiect instan
al clasei Cerc are o structur ca cea prezentat n Fig. 7.1.

Fig. 7.1
Se observ faptul c ntr-o clas derivat se specific doar membrii suplimentari care se adaug la
clasa de baz.
n secvena urmtoare:
Punct p1;
p1.SetCoord(1, 0);
Cerc c1;
c1.SetCoord(7, 9);
c1.SetRaza(2);
obiectul c1 conine un obiect ascuns al clasei Punct, care ns nu trebuie specificat prin numele su
(nu mai este nevoie de un operator de selecie suplimentar). n acest mod, membrii clasei Punct pot
fi accesai direct, ca i cnd ar fi fost declarai n clasa cerc.
Exemplul 7.2a. Acest exemplu conine o clas de baz i una derivat, care motenete membrii clasei
de baz cu specificatorul public:
# include <iostream.h>
class BAZA {
// Va fi utilizat pentru crearea clasei derivate denumit DERIVATA
int x;
// Variabil de tip private
public:
void init_x(int n) { x = n; }
void afiseaza_x() { cout << x << '\n'; }
};
class DERIVATA : public BAZA {
int y;
// Variabil de tip private
public:
void init_y(int n) { y = n; }
void afiseaza_y() { cout << y << '\n'; }
};
void main(void)
{
BAZA OB_B;
OB_B.init_x(10);
OB_B.afiseaza_x();
DERIVATA OB_D;
OB_D.init_x(100);
OB_D.init_y(200);

//
//
//
//
//
//
//

Programul principal
Se creeaz obiectul OB_B
Are acces la membrul din
Are acces la membrul din
Se creeaz obiectul OB_D
Are acces la membrul din
Are acces la membrul din
7-2

clasa de baz
clasa de baz
clasa de baz
cls. derivat

OB_D.afiseaza_x();
OB_D.afiseaza_y();

// Are acces la membrul din clasa de baz


// Are acces la membrul din cls. derivat

}
Deoarece n acest program, clasa de baz este motenit cu specificatorul public, membrii ei de tip
public - funciile init_x() i afiseaza_x() - devin membri publici ai clasei derivate. Faptul
c o clas derivat motenete public membrii din clasa de baz, nu implic i faptul c ea are acces
la membrii de tip private ai clasei de baz. Acest aspect este evideniat n exemplul urmtor:
Exemplul 7.2b. Mecanismul motenirii. Clase derivate
# include <iostream.h>
class BAZA {
// Va fi utilizat pentru crearea clasei derivate denumit DERIVATA
int x;
// Variabil de tip private
public:
void init_x(int n) { x = n; }
void afiseaza_x() { cout << x << '\n'; }
};
class DERIVATA : public BAZA {
int y;
// Variabil de tip private
public:
void init_y(int n) { y = n; }
void afiseaza_suma() { cout << x + y << '\n'; }
// Aceasta funcie nu poate avea acces la membrul privat
// x al clasei de baz
// Apare o eroare de forma BAZA :: x is not accessible
void afiseaza_y() { cout << y << '\n'; }
};
void main (void)
{
BAZA OB_B;
OB_B.init_x(10);
OB_B.afiseaza_x ();
DERIVATA OB_D;
OB_D.init_x(100);
OB_D.init_y(200);
OB_D.afiseaza_x();
OB_D.afiseaza_y();
}

//
//
//
//
//
//
//
//
//

Programul principal
Se creeaz obiectul OB_B
Are acces la membrul din
Are acces la membrul din
Se creeaz obiectul OB_D
Are acces la membrul din
Are acces la membrul din
Are acces la membrul din
Are acces la membrul din

clasa de baz
clasa de baz
clasa de baz
cls. derivat
clasa de baz
cls. derivat

Dac, n acest exemplu, clasa derivat ncearc (prin intermediul funciei membre
afiseaza_suma()) s obin accesul la variabila x, care este un membru de tip private al clasei
de baz, se semnaleaz eroarea precizat n comentariu, deoarece un membru privat al clasei de baz
rmne privat, indiferent de tipul motenirii.
n Exemplul 7.2c se consider acum c membrii clasei de baz vor fi motenii n clasa derivat cu
specificatorul private. Aceast schimbare genereaz erorile care sunt semnalate n comentariile
programului de mai jos:
Exemplul 7.2c. Mecanismul motenirii. Clase derivate
# include <iostream.h>
class BAZA {
int x;
// Variabil de tip private
public:
void init_x(int n) { x = n; }
void afiseaza_x() { cout << x << '\n'; }
};
class DERIVATA : private BAZA {
7-3

int y;
// Variabil de tip private
public:
void init_y(int n) { y = n; }
void afiseaza_y() { cout << y << '\n'; }
};
void main(void)
{
BAZA OB_B;
OB_B.init_x(10);
OB_B.afiseaza_x ();
DERIVATA OB_D;
OB_D.init_x(10);
OB_D.init_y(20);
OB_D.afiseaza_x();

// Programul principal
// Se creeaz obiectul OB_B de tip BAZA
// Are acces la membrul din clasa de baz
// Are acces la membrul din clasa de baz
// Se creeaz ob. OB_D de tip DERIVATA
// Eroare:
//'BAZA::init_x(int)' is not accessible
// Are acces la membrul din cls. derivat
// Eroare:
// 'BAZA::afiseaza_x()' is not accessible
// Are acces la membrul din cls. derivat

OB_D.afiseaza_y();
}
Comentariile ilustrez faptul c funciile init_x() i afiseaza_x() devin private pentru clasa
derivat, i deci nu sunt accesibile. Trebuie reinut c aceste funcii rmn de tip public n interiorul
clasei de baz, indiferent de modul n care sunt motenite de clasele derivate. Acest lucru se observ i
din faptul c obiectul OB_B de tip BAZA are acces la aceste funcii, n schimb obiectul OB_D de tip
DERIVATA nu are acces la aceste funcii.
O versiune a programului anterior care s elimine erorile semnalate este urmtoarea, n care membrii
publici ai clasei de baz motenii ca membri privai, sunt tratai ntr-adevr ca membri privai ai clasei
derivate:
Exemplul 7.2d. Mecanismul motenirii. Clase derivate
# include <iostream.h>
class BAZA {
int x;
// Variabil de tip private
public:
void init_x(int n) { x = n; }
void afiseaza_x() { cout << x << '\n'; }
};
class DERIVATA : private BAZA {
// Membrii publici din clasa BAZA devin membri privai
// n clasa DERIVATA
int y;
// Variabil de tip private
public:
void init_xy(int n, int m) { init_x(n); y = m; }
// Funcia init_x(int) este accesibil prin intermediul
// unei funcii membre a clasei derivate
void afiseaza_xy() { afiseaza_x(); cout << y << '\n'; }
// Funcia afiseaza_x() este accesibil prin intermediul
// unei funcii membre a clasei derivate
};
void main(void)
// Programul principal
{
BAZA OB_B;
// Se creeaz obiectul OB_B de tip BAZA
OB_B.init_x(10);
OB_B.afiseaza_x();
DERIVATA OB_D;
// Se creeaz ob. OB_D de tip DERIVATA
OB_D.init_xy(10, 20);
OB_D.afiseaza_xy();
}
7-4

n acest caz, accesul la funciile init_x(int) i afieaz_x() se face din interiorul clasei
derivate, lucru perfect legal, deoarece acestea sunt membri de tip private ai acestei clase.
7.2. Utilizarea membrilor de tip protected
Aa cum am subliniat n seciunea precedent, o clas derivat nu poate avea acces direct la membrii
de tip private din clasa de baz. Aceasta nseamn c dac o clas derivat vrea s obin accesul la
un membru al clasei de baz, atunci acel membru trebuie declarat public. Totui, pot exista situaii
cnd vrem s pstrm un membru al clasei de baz ca privat, dar s permitem unei clase derivate s
aib acces la el. Pentru a realiza acest lucru, pentru astfel de situaii, C++ folosete (n clasa de baz)
specificatorul de acces protected. Acest specificator este echivalent cu cel de tip private, cu
singura excepie c membrii de tip protected ai clasei de baz sunt accesibili membrilor oricrei
clase derivate din clasa de baz (tipul de motenire fiind public). Ei nu sunt accesibili n afara
claselor de baz i derivate.
Specificatorul de acces protected poate apare oriunde n declaraia clasei (de baz), dei se
recomand utilizarea lui dup declararea membrilor de tip private i naintea celor de tip public.
Forma general este urmtoarea:
class nume_clasa {
private:
// Opional
// Membrii de tip private
protected:
// Membrii de tip protected
public:
// Membrii de tip public
};
Cnd un membru de tip protected al unei clase de baz este motenit ca public, el devine un
membru de tip protected n clasa derivat. Dac n schimb el este motenit ca private, atunci
devine private n clasa derivat.
Menionm c specificatorul de acces protected poate fi folosit i cu structuri i uniuni.
Exemplul 7.3. Exemplu de acces la membri unei clase de tip public, private i protected
# include <iostream.h>
class MOSTRA {
int a;
// Implicit, a este de tip private
protected:
int b;
// Relativ la clasa MOSTRA, b este tot de tip private
public:
int c;
// c este de tip public
MOSTRA(int n, int m) { a = n; b = m; }
int obtine_a() { return a; }
int obtine_b() { return b; }
};
void main(void)
// Programul principal
{ MOSTRA OB(10, 20);
OB.b = 99; // Eroare: MOSTRA :: b' is not accessible
// Observaie: b este de tip protected i deci private
OB.c = 30; // OK!, variabila c este de tip public
cout << OB.obtine_a() << ' ';
cout << OB.obtine_b() << ' ' << OB.c << '\n';
}
n linia de comentariu se remarc faptul c nu se poate obine accesul la variabila b, deoarece aceasta
este de tip protected i, prin urmare, de tip private n clasa MOSTRA.

7-5

Exemplul 7.4. Programul urmtor arat ce se ntmpl cnd membrii de tip protected sunt
motenii cu specificatorul public:
# include <iostream.h>
class BAZA {
protected:
int a, b;

// a i b sunt de tip private n clasa de baz,


// dar sunt accesibili clasei derivate

public:
void init_ab(int n, int m) { a = n; b = m; }
void afiseaza_ab() { cout << a << ' ' << b << '\n'; }
};
class DERIVATA : public BAZA {
int c;
// Variabila c este privat n clasa DERIVATA
public:
void init_c(int n) { c = n; }
void afiseaza_abc() { cout << a << ' ' << b << ' ' << c <<
'\n'; }
// Acesat funcie are acces la variabilele a i b din clasa BAZA
};
void main(void)
// Programul principal
{
BAZA OB_B;
// Se creeaz obiectul OB_B de tip BAZA
OB_B.init_ab(10,20);
OB_B.afiseaza_ab(); // Se va afia 10 20
DERIVATA OB_D;
// Se creeaz ob. OB_D de tip DERIVATA
OB_D.init_ab(10, 20);
OB_D.init_c(30);
OB_D.afiseaza_abc();
// Se va afia 10 20 30
// cout << OB_B.a << " " << OB_B.a << endl;
// cout << OB_D.b << " " << OB_D.b << endl;
// Variabilele a i b nu sunt accesibile ca n instruciunile
// anterioare deoarece ele sunt de tip private att pentru
// clasa de baz ct i pentru cea derivat
}
ntruct variabilele a i b sunt declarate protected n clasa de baz, i sunt motenite ca public
de clasa derivat, ele sunt disponibile funciilor membre din clasa derivat. Totui, n exteriorul celor
dou clase, a i b sunt de tip private i deci sunt inaccesibile.
Exemplul 7.5. Programul urmtor arat modul de acces la membrii unei clase de tip public i
protected:
class A {
int p;
// p - variabila privata
public:
int q;
// q - variabila publica
protected:
int r;
// r - variabila protejata
};
class A1: A {
// Specificatorul lui A este private
int x;
public:
void f1();
};
class A2: public A {
int y;

7-6

public:
void f2();
};
n implementarea funciilor f1 i f2, membre ale claselor A1, respectiv A2, se pot utiliza direct i
membrii q i r din clasa A, ca de exemplu:
void A1::f1()
{ cout << q << endl; cout << r << endl; }
sau
void A2::f2()
{ cout << q << endl; cout << r << endl; }
n afara clasei A1, derivat din clasa A cu specificatorul private, membrii q i r nu pot fi accesai
direct, ci numai prin intermediul unei funcii membru al clasei A1; de exemplu, pentru instruciunile
de mai jos apar erorile precizate:
A1 a1;
int k = a1.q; // Eroare: 'A::q' is not accessile
int l = a1.r; // Eroare: 'A::r' is not accessile
a1.f1();
n afara clasei A2, derivat din clasa A cu specificatorul public, membrul q este public (r
rmne n continuare inaccesibil). n instruciunile de mai jos apare urmtoarea eroare:
A2 a2;
int m = a2.q;
int n = a2.r; // Eroare: 'A::r' is not accessile
a2.f2();
Observaie. Dac se dorete ca anumii membri publici din clasa de baz care au devenit privai
printr-o motenire de tip private s fie publici n clasa derivat, numele lor trebuie specificat dup
cuvntul cheie public (doar numele, nu i tipul de date). De exemplu, dac se dorete ca n clasa
A1 membrul q s fie public, declaraia clasei A1 trebuie scris astfel:
class A1: A {
int x;
public:
A::q;
void f1();
};
n acest mod, membrul q este vizibil i n exteriorul clasei A1, ceea ce nseamn c instruciunea:
int k = a1.q;
de mai sus devine valid.
7.3. Constructori i destructori n ierarhii de clase
Constructorii i destructorii sunt singurii membri ai unei clase care nu pot fi motenii ntr-o clas
derivat (n afar de operatorul de atribuire, despre care se va discuta ntr-un capitol ulterior). Acest
lucru este natural, deoarece operaiile de creare i distrugere a obiectelor unei clase sunt proprii clasei
respective i nu pot fi transmise mai departe ntr-o ierarhie de clase.
Deoarece un obiect al unei clase derivate este asimilat cu un obiect compus care are ca obiect membru
ascuns o instan a clasei de baz, constructorul pentru obiectul clasei derivate trebuie s apeleze mai
nti constructorul obiectului instan al clasei de baz. Apelul constructorului obiectului ascuns se
poate face explicit ntr-o list de iniializare a constructorului obiectului clasei derivate, sau se poate
genera implicit de ctre compilator n cazul n care clasa de baz nu are declarai constructori.
Apelul constructorului clasei de baz, n lista de iniializare a constructorului clasei derivate, se face
prin numele clasei de baz, deoarece obiectul ascuns corespunztor clasei de baz nu are nume.

7-7

Exemplul 7.6.
class Punct {
protected:
double x, y;
public:
void SetCoord(double a, double b) { x = a; y = b; }
Punct(double a = 0, double b = 0) // Constructorul cls. Punct
{ SetCoord (a, b); }
double X() const { return x; }
double Y() const { return y; }
};
class Cerc: public Punct {
double r;
public:
void SetR(double a) { r = a; }
Cerc(double a = 0, double b = 0, double c = 1):Punct(a,b), r(c)
// Construtorul cls. Cerc
{cout << "Constr. general cls. Cerc" << endl;}
Cerc(Cerc& c): Punct(c.x, c.y), r(c.r)
{ cout << "Constr. de copiere cls. Cerc" << endl;}
double R() const { return r; }
};
void main(void)
// Programul principal
{ Cerc C1(1,1,10);
Cerc C2 = C1;
cout << C2.X() << endl;
cout << C2.Y() << endl;
cout << C2.R() << endl;
C2.SetR(20);
cout << C2.R() << endl;
}
Se tie c un constructor pentru o clas poate fi generat automat de ctre compilator, n cazul n care
clasa respectiv nu are declarat un constructor.
Cnd o clas de baz i una derivat conin att funcii constructor, ct i funcii destructor, funciile
constructor sunt executate n ordinea derivrii claselor, iar cele destructor n ordine invers.
n cazul n care o clas este derivat din una sau mai multe clase de baz i toate clasele de baz au
constructori explicii ce conin parametri, clasa derivat trebuie s conin un constructor explicit care
apeleaz de asemenea explicit constructorii claselor de baz care au parametri.
Cnd iniializarea argumentelor se produce numai n clasa derivat, argumentele se transmit normal. n
schimb, dac se pune problema transmiterii unor argumente unui constructor al unei clase de baz,
trebuie stabilit un lan al transmiterii argumentelor. Mai nti sunt transmise constructorului clasei
derivate toate argumentele necesare att clasei de baz, ct i celei derivate; apoi sunt transmise clasei
de baz argumentele corespunztoare, folosindu-se o form expandat a constructorului din clasa
derivat.
Sintaxa corect necesar transmiterii unui argument din clasa derivat n clasa de baz este
urmtoarea:
constructor_clasa_derivata (lista_arg1) : constructor_clasa_de_baza (lista_arg2) {
// Corpul constructorului clasei derivate
}
cu lista_arg2 lista_arg1.
Este permis att clasei derivate, ct i clasei de baz s utilizeze acelai argument. De asemenea este
posibil ca funcia constructor al clasei derivate s ignore toate argumentele i doar s le transmit
clasei de baz.
7-8

Destructorii pentru obiectele din clasele derivate apeleaz n mod implicit destructorii obiectelor
instan ale claselor derivate n ordinea invers apelului constructorilor.
n continuare sunt prezentate o serie de exemple referitoare la utilizarea constructorilor i
destructorilor i modul de transmitere a argumentelor.
1. Programul urmtor indic ordinea de execuia a funciilor constructor i destructor.
Exemplul 7.7a.
# include <iostream.h>
class B {
// Va fi utilizat pentru crearea clasei derivate D
public:
B() { cout << "Constructor clasa de baza\n"; }
~B() { cout << "Destructor clasa de baza\n"; }
};
class D : public B {
public:
D() { cout << "Constructor clasa derivata\n"; }
~D() { cout << "Destructor clasa derivata\n"; }
};
void main(void)
// Programul principal
{ D Ob_D; }
// Se construiete obiectul OB de tip D
Pe ecran se va afia:
Constructor clasa de baza
Constructor clasa derivata
Destructor clasa derivata
Destructor clasa de baza
Se remarc apelul n ordinea derivrii claselor a funciilor constructor i n ordine invers a funciilor
destructor.
2. Programul urmtor arat modul n care este transmis un argument unui funcii constructor al clasei
derivate:
Exemplul 7.7b.
# include <iostream.h>
class B {
// Va fi utilizat pentru crearea clasei derivate D
public:
B() { cout << "Constructor clasa de baza\n"; }
~B() { cout << "Destructor clasa de baza\n"; }
};
class D : public B {
int j;
// Variabila j este privat n clasa D
public:
D(int n) { // Constr. clasei D conine n list parametrul n
cout << "Constructor clasa derivata\n";
j = n;
}
~D() { cout << "Destructor clasa derivata\n"; }
void afiseaza_j() { cout << "j = " << j << endl; }
};
void main (void)
// Programul principal
{ D Ob_D (10);
Ob_D.afiseaza_j();
}
Pe ecran se va afia:
Constructor clasa de baza
Constructor clasa derivata
7-9

j = 10
Destructor clasa derivata
Destructor clasa de baza
Se observ c argumentul n este transmis funciei constructor al clasei derivate ntr-o manier uzual.
3. n exemplul care urmeaz, att constructorul clasei de baz, ct i cel al clasei derivate conin un
acelai argument. Clasa derivat utilizeaz, dar i transmite argumentul clasei de baz:
Exemplul 7.7c.
# include <iostream.h>
class B {
// Va fi utilizat pentru crearea clasei derivate D
int i;
public:
B(int n) { cout << "Constructor clasa de baza\n";
i = n;
}
~B() { cout << "Destructor clasa de baza\n"; }
void afiseaza_i() { cout << "i = " << i << endl; }
};
class D : public B {
int j;
public:
D(int n) : B(n) {
// Constructorul clasei derivate
cout << "Constructor clasa derivata\n";
j = n;
}
~D() { cout << "Destructor clasa derivata\n"; }
void afiseaza_j() { cout << "j = " << j << endl; }
};
void main(void) {
// Programul principal
D Ob_D (10);
Ob_D.afiseaza_i();
Ob_D.afiseaza_j();
}
Pe ecran se va afia:
Constructor clasa de baza
Constructor clasa derivata
i = 10
j = 10
Destructor clasa derivata
Destructor clasa de baza
Se vede c parametrul n este utilizat de clasa derivat i este transmis i clasei de baz.
4. n majoritatea cazurilor constructorii din clasa de baz i din cea derivat nu vor utiliza acelai
argument. ntr-o astfel de situaie, cnd fiecrui constructor trebuie s-i transmitem unul sau mai multe
argumente, este necesar s transmitem constructorului din clasa derivat toi parametri necesari att
clasei de baz, ct i celei derivate; apoi clasa derivat transmite clasei de baz argumentele necesare
acesteia. Un exemplu, n acest sens, este prezentat n listingul urmtor:
Exemplul 7.7d.
# include <iostream.h>
class B {
// Va fi utilizat pentru crearea clasei derivate D
int i;
public:
B(int n) { cout << "Constructor clasa de baza\n";
i = n;
}
~B() { cout << "Destructor clasa de baza\n"; }
7 - 10

void afiseaza_i() { cout << "i = " << i << endl; }


};
class D : public B {
// Implementarea clasei derivate D
int j;
public:
D(int n, int m) : B(m) { //Paseaz argumentul m clasei de baz B
cout << "Constructor clasa derivata\n";
j = n;
}
~D() { cout << "Destructor clasa derivata\n"; }
void afiseaza_j() { cout << "j = " << j << endl; }
};
void main (void)
// Programul principal
{ D Ob_D(10, 40);
// Se creeaz obiectul Ob_D de tip D
Ob_D.afiseaza_i();
Ob_D.afiseaza_j();
}
Pe ecran se va afia:
Constructor clasa de baza
Constructor clasa derivata
i = 40
j = 10
Destructor clasa derivata
Destructor clasa de baza
5. Este important de neles c este necesar ca funcia constructor din clasa derivat s aib argumente
pentru a le transmite constructorului clasei de baz (dac acesta conine argumente). Dac clasa
derivat nu are nevoie de nici un argument, ea le ignor i doar le transmite clasei de baz. n
programul urmtor parametrul n nu este utilizat de constructorul clasei derivate; el este numai transmis
constructorului clasei de baz.
Exemplul 7.7e.
# include <iostream.h>
class B {
// Va fi utilizat pentru crearea clasei derivate D
int i;
public:
B(int n) { cout << "Constructor clasa de baza\n";
i = n;
}
~B() { cout << "Destructor clasa de baza\n"; }
void afiseaza_i() { cout << "i = " << i << endl; }
};
class D : public B {
public:
D(int n) : B(n) {
// Paseaz argumentul clasei de baz
cout << "Constructor clasa derivata\n";
}
~D() { cout << "Destructor clasa derivata\n"; }
};
void main(void)
// Programul principal
{ D Ob_D (10);
// Se creeaz obiectul Ob_D de tip D
Ob_D.afiseaza_i();
}

7 - 11

7.4. Mecanismul motenirii multiple


Exist dou modaliti prin care o clas derivat poate moteni de la mai multe clase:
1. Prima modalitate se refer la faptul c o clas derivat poate fi folosit drept clas de baz pentru o
alt clas derivat, crendu-se astfel o ierarhie de clase. n acest caz, clasa de baz iniial este o
clas de baz indirect a celei de-a doua clase derivate.
2. A doua modalitate se refer la faptul c o clas derivat poate moteni n mod direct de la mai
multe clase de baz. La utilizarea acestui tip de mecanism se ridic o serie de probleme pe care le
vom aborda n acest paragraf.
Cnd o clas de baz este folosit pentru a se deriva din ea o alt clas, care la rndul ei este utilizat
drept clas de baz pentru a se deriva alt clas, funciile constructor ale celor trei clase sunt apelate n
ordinea derivrii, iar funciile destructor sunt apelate n ordine invers. Se observ c se generalizeaz
mecanismul precizat anterior. Astfel, dac clasa B1 este motenit de clasa D1, iar la rndul su
aceasta este motenit de clasa D2, atunci se apeleaz mai nti, constructorul clasei B1, urmat de cel
al clasei D1 i, n final, constructorul clasei D2. Destructorii sunt apelai n ordinea invers.
Cnd o clas derivat motenete direct de la mai multe clase de baz, se utilizeaz urmtoarea
declaraie expandat:
class nume_clasa_derivata : specificator_acces nume_clasa_de_baza_1,
specificator_acces nume_clasa_de_baza_2,
..., specificator_acces nume_clasa_de_baza_N
{ // Corpul clasei
};
Precizm c specificatorul de acces poate fi diferit pentru fiecare clas de baz. Cnd sunt motenite
mai multe clase de baz, funciile constructor sunt apelate n ordine de la stnga la dreapt, funciile
destructor fiind apelate n ordine invers. Clasa derivat transmite claselor de baz argumentele
necesare, prin utilizarea urmtoarei forme expandate a constructorului clasei derivate:
constructor_clasa_derivata (lista_argumente) : BAZA_1 (lista_argumente_1),
BAZA_2 (lista_argumente_2),
..., BAZA_N (lista_argumente_N)
{ // Corpul constructorului din clasa derivat }
unde BAZA_1, ..., BAZA_N reprezint numele constructorilor claselor de baz.
Pentru claritate, n continuare, se prezint o serie de exemple:
1. Primul exemplu se refer la o motenire multipl, cnd o clas derivat motenete de la o clas
derivat din alt clas.
Exemplul 7.8a. Mecanismul motenirii multiple. Clase derivate
# include <iostream.h>
class B1 {
// Va fi utilizat pentru crearea clasei derivate D1
int a;
public:
B1(int x) { a = x; }
int obtine_a() { return a; }
};
class D1 : public B1 {
// D1 mostenete clasa de baz B1
int b;
public:
D1(int x, int y) : B1(y)
// Constructorul clasei D1 paseaz
// parametrul y clasei B1
{ b = x; }
int obtine_b() { return b; }
};
class D2 : public D1 {
// D2 moteneste att de la o clas
// derivat, ct i de la una indirect
int c;
7 - 12

public:
D2(int x, int y, int z) : D1(y, z)
// Se paseaz argumentele clasei D1
{ c = x;
}
// ntruct motenete public membrii claselor de baz, clasa D2 are
// acces la elementele de tip public, att din clasa B1,
// ct i din clasa D1
void afiseaza() {
cout << obtine_a() << ' ' << obtine_b() << ' ' << c << endl;
}
};
void main(void)
// Programul principal
{
D2 OB(10, 20, 30);
// Se construiete obiectul OB de tip D2
OB.afiseaza();
cout << OB.obtine_a() << ' ' << OB.obtine_b() << endl;
}
Apelul funciei OB.afiseaza() genereaz rezultatul 30 20 10. n acest exemplu, B1 este o clas de
baz indirect pentru clasa D2. Se observ c D2 are acces la membrii de tip public att ai clasei
D1, ct i ai clasei B1. Reamintim c dac n mecanismul motenirii se utilizeaz specificatorul public,
membrii publici ai clasei motenite devin membri publici ai clasei derivate. Prin urmare, cnd clasa D1
a motenit clasa B1, funcia obine_a() a devenit membru de tip public a clasei D1, care la
rndul ei, este membru public a clasei D2. Aa cum arat programul, fiecare clas din ierarhia de clase
trebuie s transmit toate argumentele cerute de fiecare clas precedent, altfel se va semnala o eroare
la momentul compilrii.
2. Modificm programul anterior astfel nct o clas derivat s moteneasc de la dou clase de baz.
Exemplul 7.8b. Mecanismul motenirii multiple. Clase derivate
# include <iostream.h>
class B1 {
// Clasa de baza B1
int a;
public:
B1(int x)
{ a = x; }
int obtine_a() { return a; }
};
class B2 {
// Clasa de baza B2
int b;
public:
B2(int x) { b = x; }
int obtine_b() { return b; }
};
class D : public B1, public B2 {
// Mostenete direct de la cele dou clase de baz B1 i B2
int c;
public:
D (int x, int y, int z) : B1(z), B2(y)
// Variabilele z i y sunt pasate direct claselor B1 i B2
{ c = x; }
// ntruct membrii claselor de baz au fost motenii ca public,
// clasa D are acces la elementele de tip public, att din clasa B1,
// ct i din clasa B2
void afiseaza() {
cout << obtine_a() << ' ' << obtine_b() << ' ' << c << endl;
}

7 - 13

};
void main(void)
// Programul principal
{ D OB(10, 20, 30); // Se construiete obiectul OB de tip D
OB.afiseaza();
cout << OB.obtine_a() << ' ' << OB.obtine_b() << endl;
}
Argumentele y i z sunt transmise individual claselor B1 i B2 de ctre funcia constructor al clasei
D.
3. Programul urmtor ilustreaz ordinea de apel a funciilor constructor i destructor, cnd o clas
derivat motenete, n mod direct, de la mai multe clase de baz:
Exemplul 7.8c. Mecanismul motenirii multiple. Apelul constructorilor i destructorilor
# include <iostream.h>
class B1 {
// Clasa de baz B1
public:
B1() { cout << "Constructor B1\n"; }
~B1() { cout << "Destructor B1\n"; }
};
class B2 {
// Clasa de baz B2
public:
B2() { cout << "Constructor B2\n"; }
~B2() { cout << "Destructor B2\n"; }
};
class D : public B1, public B2 {
// Mostenete direct de la cele dou clase de baz B1 i B2
public:
D() { cout << "Constructor D\n"; }
~D() { cout << "Destructor D\n"; }
};
void

main(void)
{ D OBJ; }

// Programul principal
// Se creeaz obiectul OBJ de tip D

Pe ecran se va afia:
Constructor B1
Constructor B2
Constructor D
Destructor D
Destructor B2
Destructor B1
Se observ c atunci cnd sunt motenite direct mai multe clase de baz, funciile constructor sunt
apelate n ordine de la stnga la dreapta, dup cum se specific n lista de motenitori.
Observaie: n cazul motenirii multiple, apelul constructorilor obiectelor instan al claselor de baz
se face n ordinea specificrii claselor de baz n declaraia clasei derivate, iar apelul destructorilor n
ordine invers.
Exemplul 7.9. Se va utiliza o macrodefiniie pentru definirea unor clase:
#include <iostream.h>
#define CLASS(ID) class ID {\
public:\
ID(int k){cout<<Constructor clasa <<#ID<< <<k<<endl;}\
~ID(){cout<<Destructor clasa <<#ID<<endl;}\
};
CLASS(B1);
// Se defineste clasa B1
7 - 14

CLASS(B2);
CLASS(M1);
CLASS(M2);
class D: public B1, B2 {
M1 m1;
M2 m2;
public:
D(int): m1(10), m2(20), B1(30), B2(40) {
cout << Constructor clasa D << endl;
}
~D() { cout << Destructor clasa D << endl; }
};
void main() {
D d(0);
// Se declara un obiect de tip D
}
Se va afia:
Constructor clasa B1
Constructor clasa B2
Constructor clasa M1
Constructor clasa M2
Constructor clasa D
Destructor clasa D
Destructor clasa M2
Destructor clasa M1
Destructor clasa B2
Destructor clasa B1

30
40
10
20

n cazul n care o clas derivat nu posed un constructor de copiere propriu, acesta va fi generat n
mod automat de ctre compilator. El apeleaz constructorii de copiere ai claselor de baz, urmat, dac
este cazul, de apelul constructorilor de copiere ai obiectelor membre ale clasei (sau ai pseudoconstructorilor n cazul tipurilor predefinite).
Exemplul 7.10. Utilizarea constructorilor de copiere.
#include <iostream.h>
class Baza {
int n;
public:
Baza(int i): n(i) {
cout << Baza(int i) << << i << endl;
}
Baza(const Baza& b): n(b.n) {
cout << Baza(const Baza& b) << endl;
}
Baza(): n(0) { cout << Baza() << endl; }
void Print() const {
cout << Baza; n = << n << endl;
}
};
class Membru {
int n;
public:
Membru(int i): n(i) {
cout << Membru(int i) << << i << endl;
}
Membru(const Membru& m): n(m.n) {
7 - 15

cout << Membru(const Membru& m) << endl;


}
void Print() const {
cout << Membru; n = << n << endl;
}
};
class Derivata: public Baza {
int n;
Membru m;
public:
Derivata(int i): Baza(i), n(i), m(i) {
cout << Derivata(int i) << << i << endl;
}
void Print() const {
cout << Derivata; n = << n << endl;
Baza::Print();
m.Print();
}
};
void main() {
Derivata o1(7);
cout << Apel constructor de copiere: << endl;
Derivata o2 = o1;
cout << Valori in o2: << endl;
o2.Print();
}
Se va afia:
Linia 1:
Linia 2:
Linia 3:
Linia 4:
Linia 5:
Linia 6:
Linia 7:
Linia 8:
Linia 9:
Linia 10:

Baza(int i) 7
Membru(int i) 7
Derivata(int i) 7
Apel constructor de copiere:
Baza(const Baza& b)
Membru(const Membru& m)
Valori in o2:
Derivata; n = 7
Baza; n = 7
Membru; n = 7

Constructorii de copiere pentru clasele Baza i Membru au fost apelai implicit (liniile 5 i 6),
precum i pseudo-constructorul de copiere pentru membrul n (n linia 10, valoarea componentei n a
obiectului o2 este tot 7, ca i cea din obiectul o1).
n cazul n care n clasa derivat se dorete scrierea unui constructor de copiere, apelul constructorului
pentru clasa de baz trebuie s fie explicit. De exemplu, un constructor de copiere pentru clasa
Derivata poate fi urmtorul:
Derivata(const Derivata& d): Baza(d), n(d.n), m(d.n) { }
Ieirea programului va fi n acest caz la fel cu cea din exemplul precedent.
Observaie: Parametrul constructorului de copiere pentru clasa Baza este o referin la un obiect al
clasei derivate, ceea ce este o operaie corect n cazul motenirii publice (clasa Derivata este un
subtip al clasei Baza). n cazul motenirii private, este indicat ca un asemenea constructor de copiere
s fie apelat implicit de ctre compilator.

7 - 16

7.5. Motenirea public i privat


Utilizarea tipului de motenire public sau privat are o semnificaie distinct n proiectarea i
dezvoltarea unor aplicaii.
A. Motenirea public
Motenirea public motenirea n care se utilizeaz specificatorul public este legat n special
de etapa de proiectare a unei aplicaii, n cazul n care se dorete ca o anumit clas s reprezinte o
specializare a clasei de baz. De exemplu, clasa student este o specializare a clasei persoana, n
sensul c orice student este o persoan.
Acest tip de motenire presupune motenirea ntr-o clas derivat att a interfeei clasei de baz ct i a
implementrii acesteia; n acest mod un obiect al clasei derivate putnd fi utilizat n locul unui obiect
al clasei de baz.
Exemplul 7.11. Relum exemplul 7.6 relativ la clasele Punct i Cerc n care se introduce funcia
Distanta care determin distana ntre dou puncte:
class Punct {
protected:
double x, y;
public:
void SetCoord(double a, double b) { x = a; y = b; }
Punct(double a = 0, double b = 0) // Constructorul cls. Punct
{ SetCoord (a, b); cout << "Constr. clasa Punct" << endl;
}
double X() const { return x; }
double Y() const { return y; }
};
class Cerc: public Punct {
double r;
public:
void SetR(double a) { r = a; }
Cerc(double a = 0, double b = 0, double c = 1):Punct(a,b), r(c)
// Construtorul cls. Cerc
{cout << "Constr. clasa Cerc" << endl;}
};
double Distanta(Punct& p1, Punct& p2) {
double d = sqrt((p1.X()-p2.X())*(p1.X()-p2.X())+
(p1.Y()-p2.Y())*(p1.Y()-p2.Y()));
return d;
}
void main(void) {
// Programul principal
Punct p1, p2;
p1.SetCoord(1, 1);
p2.SetCoord(0, 0);
Cerc c1, c2;
c1.SetCoord(7, 7);
c2.SetCoord(3, 3);
double d1 = Distanta(p1, p2);
// Corect
double d2 = Distanta(c1, c2);
// Corect
cout << d1 << endl << d2 << endl;
}
Programul este corect i la sfrit va afia valorile corespunztoare lui 2 i lui 32 .
Datorit faptului c o clas derivat public motenete att declaraia, ct i implementarea clasei de
baz, ntr-o clas derivat se pot redefini membrii din clasa de baz. Redefinirea unui membru
presupune ascunderea n clasa derivat a membrului din clasa de baz cu acelai nume.
7 - 17

Exemplul 7.12.
class A {
public:
void f() const {
cout << f in clasa A << endl;
}
};
class B: public A {
public:
void f() const {
cout << f in clasa B << endl;
}
};
void main (){
A a;
a.f();
//f din clasa A
B b;
b.f();
//f din clasa B
b.A::f();
//f din clasa A
}
n cazul n care se dorete utilizarea membrului ascuns, acesta trebuie prefixat de numele clasei de
baz.
Observaie: Redefinirea unor membri dintr-o clas de baz ntr-o clas derivat nu este indicat, ea
presupunnd o eroare de proiectare a ierarhiei de clase. n cazul n care se dorete acest lucru, vor
trebui utilizate funcii virtuale.
B. Motenirea de tip private
Motenirea privat motenirea n care se utilizeaz specificatorul private este specific etapei
de dezvoltare a unei aplicaii, cnd se dispune de anumite ierarhii de clase deja proiectate.
n cazul motenirii private, toi membrii publici ai clasei de baz devin membri private n clasa
derivat, ceea ce i face s nu poat fi utilizai n exteriorul clasei derivate (menionm c membrii
private din clasa de baz, devin inaccesibili n clasa derivat). Cu alte cuvinte, n acest caz o clas
derivat motenete doar implementarea clasei de baz, nu i interfaa acesteia.
n cazul motenirii private, un obiect din clasa derivat nu mai este convertit de ctre compilator ntrun obiect al clasei de baz (clasa derivat nu mai reprezint un subtip al clasei de baz).
Exemplul 7.13. Reluarea exemplului cu clasele Punct i Cerc:
class Punct {
protected:
double x, y;
public:
void SetCoord(double a, double b) {
x = a; y = b;
}
Punct(double a = 0, double b = 0) {
SetCoord(a, b);
}
double X() const { return x; }
double Y() const { return y; }
};
class Cerc: Punct {
// Mostenire privata a clasei Punct
public:
double r;
void SetR(double a = 1) { r = a; }
7 - 18

Cerc(double a = 0, double b = 0, double c = 1):


Punct(a,b),r(c) { }
};
double Distanta(Punct p1, Punct p2) {
double d = sqrt((p1.X()-p2.X())*(p1.X()-p2.X())+
(p1.Y()-p2.Y())*(p1.Y()-p2.Y()));
return d;
}
void main(){
Punct p1(0, 0), p2(1, 1);
Cerc c1(3, 3, 1), c2(7, 7, 2);
double d1 = Distanta(p1, p2); // Corect
double d2 = Distanta(c1, c2); // eroare!!!
cout << d1 << endl;
// Corect
cout << c1.r << endl; cout << c2.r << endl;
}
Observaie. Compunerea obiectelor este similar motenirii private, deoarece pentru un membru
obiect ntr-o clas compus se motenete doar implementarea, nu i interfaa acestuia. De aceea, ori
de cte ori este posibil, este indicat utilizarea compunerii obiectelor n locul motenirii private.
Utilizarea motenirii private este necesar doar n cazurile n care clasa de baz conine membri de tip
protected, care altfel nu pot fi uilizai n clasa derivat.
C. Conversia de tip ntre clasele de baz i clasele derivate n cazul motenirii publice
O proprietate important a relaiei de derivare o constituie faptul c o clas derivat public este
tratat ca un subtip al clasei de baz. n acest mod, obiectele instan ale clasei derivate sunt
compatibile cu obiectele clasei de baz i pot fi utilizate n instruciuni de atribuire.
Pentru exemplul precedent al claselor Cerc i Punct, urmtoarea instruciune de atribuire este
corect:
Punct p3;
// Clasa de baza
Cerc c3;
// Clasa derivata
p3 = c3;
Observaie: Atribuirea poate funciona doar ntr-o singur direcie i anume unui obiect al clasei de
baz i se poate atribui un obiect al clasei derivate, nu i invers. Deci, nu se poate atribui un obiect al
unei clase de baz unui obiect al unei clase derivate. De exemplu, instruciunea urmtoare este greit:
c3 = p3;
Regula compatibilitii la atribuire se extinde i la pointeri i referine de obiecte. De exemplu,
urmtoarea secven de program este corect:
class B { // ... };
class A1: public B { // ... };
class A2: public B { // ... };
A1 a1;
A2 a2;
B *pb;
// pb pointer de tip B
pb = &a1;
pb = &a2;
n exemplul precedent exist o conversie implicit de tip de la un pointer al unei clase derivate la un
pointer al clasei de baz. O asemenea conversie, de la un pointer (sau o referin) la o clas derivat
spre un pointer (sau o referin) la clasa de baz este numit upcasting.
Denumirea vine de la conversia implicit de tip (cast) i de la sensul de conversie din ierarhia de
clase (up), de la o clas din partea de jos a unei ierarhii spre o clas din partea de sus.
Operaia de upcasting este frecvent utilizat n aplicaiile ce folosesc ierarhii de clase, permind o
tratare uniform a obiectelor dintr-o asemenea ierarhie prin intermediul pointerilor la clasa de baz.

7 - 19

De exemplu, n cazul motenirii publice ntre clasele Cerc i Punct, utilizarea funciei Distanta
se bazeaz pe upcasting:
double Distanta(Punct& p1, Punct& p2) {
// ...
}
Cerc c1(7, 3), c2(2, 2);
// upcasting de la Cerc& la Punct&
double d = Distanta(c1, c2);
7.6. Probleme ale moteniirii multiple
Motenirea multipl apare n cazul n care o anumit clas derivat motenete (public sau privat) mai
multe clase de baz. Exist dou probleme importante care pot apare n cazul utilizrii motenirii
multiple: duplicarea obiectelor ascunse i existena unor membri cu acelai nume n clase de baz
diferite.
Duplicarea obiectelor ascunse poate apare n cazul ierarhiilor care au o form ca cea din figura
urmtoare (numite i ierarhii de tip diamant, datorit formei lor).
B

D1

D2

D3
Fig. 7.2.
Datorit faptului c un obiect al unei clase derivate posed un sub-obiect ascuns al clasei de baz,
pentru o ierarhie de forma:
class B {
// ...
};
class D1: public B {
int a;
// ...
};
class D2: public B {
int b;
// ...
};
class D3: public D1, public D2 {
int m;
// ...
};
un obiect insta al clasei D3 contine dou sub-obiecte ascunse identice, corespunztoare clasei B.
Acest lucru poate determina o ambiguitate cnd clasa D3 vrea s aib acces la clasa de baz B. ntruct
exist dou copii ale clasei B, n clasa D3, o referire la un membru din clasa B se refer la acela care a
fost motenit indirect prin intermediul clasei D1, sau la membrul motenit indirect, prin intermediul
clasei D2 ? Pentru a rezolva aceast ambiguitate, C++ include un mecanism prin care numai o singur
copie a clasei B va fi inclus n clasa D3. Acest mecanism se numete clas de baz virtual. Astfel,

7 - 20

prin utilizarea cuvntului cheie virtual, naintea specificatorului de acces, se previne fenomenul ca
dou copii ale clasei de baz s se gseasc n clasa derivat. Acest aspect este prezentat n exemplul
urmtor:
Exemplul 7.14. Mecanismul motenirii multiple. Clase de baz virtuale
# include <iostream.h>
class B {
// Clasa de baza B1
public:
int i;
};
class D1 : virtual public B {
public:
int j;
};

// Motenete clasa de baz virtual

class D2 : virtual public B {


public:
int k;
};

// Motenete clasa de baz virtual

class D3 : public D1, public D2


// Motenete att de la clasa D1, ct i de la clasa D2
{
public:
int produs() { return i*j*k; }
};
void main(void)
// Programul principal
{
D3 OBJ;
// Se construiete ob. OBJ de tip D3
OBJ.i = 10;
OBJ.j = 30;
OBJ.k = 50;
cout << "Produsul i*j*k = " << OBJ.produs() << '\n';
OBJ.i = 2;
OBJ.j = 3;
OBJ.k = 5;
cout << "Produsul i*j*k = " << OBJ.produs() << '\n';
}
Observaie. Dac clasele D1 i D2 n-ar fi motenit clasa de baz drept virtual, la compilare s-ar fi
semnalat o serie de erori.
Principala problem a motenirii multiple este ns generat de cazul n care mai multe clase de baz
posed un membru cu acelai nume: cum vor putea fi utilizai aceti membri n clasa derivat ?
Exemplul 7.15.
class B1 {
public:
int a;
// ...
};
class B2 {
public:
double a;
// ...
};
class D: public B1, public B2 {
// ...
};
7 - 21

void main() {
D d;
d.a = 5; //eroare!!
}
n exemplul precedent, instruciunea d.a = 5 genereaz o eroare datorit confuziei ce apare la
selecia membrului a.
n acest caz pentru evitarea confuziei se va utiliza operatorul de rezoluie cu care se va explicita
membrul cu acelai nume. De exemplu, instruciunea care genereaz eroarea ar putea fi rescris sub
forma:
d.B1::a = 5;
sau
d.B2::a = 5.5;

7 - 22

S-ar putea să vă placă și