Sunteți pe pagina 1din 46

PCL - 2 : 1 ELTC –B

Modul 2
Curs 8
Cuprins
1. Moştenirea
• Constructori şi destructori pentru clasa derivată
2. Clase virtuale
3. Pointeri către clasa de bază
4. Metode virtuale
5. Clase abstracte

2
1. Moştenirea
Constructori şi destructori pentru clasa derivată
• Regulile de funcţionare ale constructorilor şi destructorilor
rămân valabile pentru clasele derivate, cu unele aspecte
particulare
• Ce se întâmplă la crearea unui obiect din clasa derivată:
– se alocă spaţiu pentru întregul obiect (atât pentru datele membre
moștenite din clasa de bază cât si datele membre noi din clasa
derivată)
– se apelează constructorul clasei de bază pentru a iniţializa datele
membre moştenite din clasa de bază
– se apelează constructorul din clasa derivată pentru a iniţializa
datele membre adăugate în clasa derivată

• Ce se întâmplă la distrugerea unui obiect din clasa


derivată:
– se apelează destructorul clasei derivate
– se apelează destructorul clasei de bază
– spaţiul alocat pentru întregul obiect este eliberat 3
class A {
// corp clasa
};
class B : public class A {
// corp clasa
};
class C : public class B {
// corp clasa
};

• In cazul instanţierii unui obiect din clasa C, ordinea


apelurilor constructorilor:
A::A( ) -> B::B( ) -> C::C( );

• Ordinea distrugerii obiectelor (apelurile destructorilor)


este:
C::~C( ) -> B::~B( ) -> A::~A( ); 4
• Dacă constructorul unei clase de bază necesită
parametri atunci constructorul clasei derivate trebuie să
specifice apelul constructorului clasei de bază în lista
de iniţializare
• De regulă constructorul clasei derivate conţine în lista
de parametri şi acei parametri care vor fi transmişi
constructorului clasei de bază:
clasa_derivata ::clasa_derivata(lista_parametri) : clasa_baza(parametri);

• Exemplu:
B::B(int x, int y) : A(y) {
// corp constructor
}

5
Exemplu
// clasa de baza
class Pozitie {
protected :
int x, y;
public :
Pozitie (int=0, int=0);
void afisare( );
void deplasare(int dx, int dy);
};

Pozitie::Pozitie(int abs, int ord) {


x = abs; y=ord;
cout << "Constructor \"Pozitie\", ";
afisare( );
}
void Pozitie::afisare( ) {
cout << "coordonate: x = " << x << ", y = " << y << "\n";
}

void Pozitie::deplasare(int dx, int dy) {


x += dx; y+=dy;
} 6
// clasa derivata
class Punct: public Pozitie {
int vizibil;
char culoare;
public:
Punct(int abs=0, int ord=0, char c='A');
void arata( ) {
vizibil = 1;
}
void ascunde( ) {
vizibil = 0;
}
void coloreaza(char c) {
culoare = c;
}
void deplasare(int dx, int dy);
void afisare( );
};
7
// constructor
Punct::Punct(int abs, int ord, char c) : Pozitie(abs, ord)
{
vizibil = 0;
culoare = c;
cout << "Constructor \"Punct\", ";
afisare( );
}

// redefinire functie de afisare in clasa derivata


void Punct::afisare( ) {
cout << "Pozitie: x = " << x << ", y = " << y;
cout << ", culoare: " << culoare;
if(vizibil)
cout << ", vizibil \n";
else
cout << ", invizibil \n";
}

8
// redefinire functie de deplasare in clasa derivata
void Punct::deplasare(int dx, int dy)
{
if(vizibil) {
cout << "Deplasare: ";
afisare( ); // apel metoda din clasa Punct
}
x += dx;
y += dy;
if(vizibil) {
cout << "la coordonatele: ";
Pozitie::afisare( ); // apel metoda din clasa Pozitie
}
}

9
// program ce utlizeaza clasa derivata
int main()
{
Punct p0(1, 1, 'V');
Punct p1(p0);
p1.arata( );
p1.deplasare(10,10);
}

10
• Constructorul de copiere al clasei derivate este
răspunzător de iniţializarea corectă a datelor moştenite
de la clasa de bază
• De aceea se recomandă utilizarea unui astfel de
constructor de copiere:
clasa_deriv ::clasa_deriv(clasa_derivata &nume_par) : clasa_baza(parametri);

11
• In cazul moştenirii multiple, ordinea în care sunt apelaţi
constructorii claselor de bază este aceiaşi cu ordinea
specificării claselor de bază în declaraţia clasei derivate
– această ordine poate diferi de ordinea specificată în lista de
iniţializare din constructorul clasei derivate

• In general, ordinea operaţiilor în cazul creării obiectelor


derivate este următoarea:
– se apelează constructorii claselor de bază în ordinea precizată
în declaraţia clasei derivate
– se apelează constructorii altor obiecte membre, în ordinea
declarării lor în clasa derivată
– se apelează constructorii clasei derivate

• La distrugerea obiectelor derivate, operaţiile se


desfăşoară în ordine inversă

12
2. Clase virtuale
• Considerăm următoarea situaţie:
class BB {
public : int x;

};
class B1 : public BB {
// corp clasa
};
class B2 : public BB {
// corp clasa
};
class D : public B1, public B2 {
// corp clasa
};

• Un obiect al ultimei clase derivate D conţine duplicate ale


datelor membre ale primei clase de bază BB 13
• Pentru a distinge între datele membre se poate folosi
operatorul de rezoluţie (::) şi calificarea completă astfel:
BB::B1::x
BB::B2::x

• Dacă o instanţă a clasei D ar vrea să acceseze variabila


x printr-o sintaxă de genul:
D obiect;
obiect.x = valoare;

• Apare o ambiguitate:
– compilatorul nu poate decide prin care din clasele B1 sau B2 să
acceseze variabila din clasa de bază

14
• Există posibilitatea includerii unui singur exemplar al clasei
de bază primare prin declararea acesteia ca şi clasă virtuală
în declaraţiile claselor B1 si B2:

class BB {
protected : int x, y;

};

class B1 : virtual public BB {


// corp clasa
};
class B2 : virtual public BB {
// corp clasa
};

class D : public B1, public B2 {


// corp clasa
};
15
• In acest caz, un obiect al clasei D va conţine numai un
obiect al clasei de bază comune BB iar clasele B1 şi B2
vor partaja acest obiect comun

• Un obiect al unei clase derivate dintr-o clasă de bază


virtuală nu conţine direct obiectul clasei de bază comune
ci conţine de fapt un pointer către acesta

16
• Dacă într-o ierarhie de clase, unele instanţe ale clasei de
bază sunt declarate virtuale iar altele sunt declarate non-
virtuale, atunci obiectele claselor derivate vor conţine câte
un obiect al clasei de bază pentru fiecare instanţă non-
virtuală şi un singur obiect al clasei de bază pentru toate
instanţele virtuale

• Specificatorul virtual deşi apare în declaraţiile claselor B1


şi B2 are efect numai asupra claselor derivate din
acestea

17
• Constructorul clasei derivate D (ultima clasă derivată)
trebuie să precizeze transferul de date către
constructorul clasei de bază BB pentru crearea unei
copii unice a obiectului BB:
D::D(lista_param) : B1(Lista_param), B2(lista_param),
BB(lista_param) {…}

• Indiferent de poziţia într-o ierarhie de clase, un obiect al


unei clase de bază virtuale este întotdeauna construit
înaintea obiectelor non-virtuale din aceeaşi ierarhie

• Dacă într-o ierarhie de clase, o clasă de bază este


declarată virtuală folosind specificatori de acces diferiţi,
atunci se va propaga specificatorul cel mai permisiv (de
exemplu dintre specificatorii private şi public se va
propaga specificatorul public)
18
Exemplul 1:
class base {
int i;
public:
base(int x) : i(x) { }
int geti( ) { return i; }
};

class derived1 : virtual public base {


int j;
public:
derived1(int x, int y) : base(x), j(y) { }
int getj( ) { return j; }
};

class derived2 : virtual public base {


int k;
public:
derived2(int x, int y) : base(x), k(y) { }
int getk( ) { return k; }
}; 19
class derived3 : public derived1, public derived2 {
public:
derived3(int x, int y, int z) : derived1(x, y), derived2(x, z), base(x) { }
int product( ) {
return (geti( )*getj( )*getk( ));
}
};

int main()
{
derived3 ob(10, 20, 30);
cout << "Product is: " << ob.product( ) << "\n";
}

20
Exemplul 2:
class Person
{
private:
string ptype;
string name;
public:
Person( const string & t, const string & n ) : ptype( t ), name( n )
{}

const string & getName( ) {


return name;
}

const string getPtype( ) {


return ptype;
}
};

21
class Student : virtual public Person
{
private:
int hours;
public:
Student( const string & n, int h ) : Person( "Student", n ), hours(h)
{}

int getCreditHours( ) {
return hours;
}
};

22
class Employee : virtual public Person
{
private:
int hours;
public:
Employee( const string & n, int h ) : Person( "Employee", n), hours(h)
{}

int getVacationHours( ) {
return hours;
}
};

23
class StudentEmployee : public Student, public Employee
{
public:
StudentEmployee( const string & n, int ch, int vh ) :
Person( "StudentEmployee", n ),
Student( "ignored", ch ),
Employee( "ignored", vh )
{}
};

24
int main( )
{
StudentEmployee se( "Bitang", 120, 15 );

cout << "Name: " << se.getName( ) << endl;


cout << "Type: " << se.getPtype( ) << endl;
cout << "Credits: " << se.getCreditHours( ) << endl;
cout << "Vacation: " << se.getVacationHours( ) << endl;
}

25
3. Pointeri către clasa de bază
• Un pointer către o clasă derivată este compatibil ca şi tip
cu un pointer la clasa de bază din care este derivată:
– pointerii către clasa de bază pot fi folosiţi pentru a accesa membrii
din clasele derivate ce sunt moşteniţi din clasa de bază

26
class Base
{
protected:
int b;
public:
Base(int k) {
b = k;
}
void show() {
cout << "Din clasa de baza: b = " << b << endl;
}
};

27
class Derived : public Base
{
protected:
int d;
public:
Derived(int j, int k) : Base(k)
{
d = j;
}
void show() {
cout << "Din clasa derivata: b = " << b << ", d = " << d << endl;
}
};

28
int main()
{
Base b(20), *bp=&b;
bp->show();

Derived d(10, 20);


bp = &d;
bp->show();

((Derived *)bp)->show();

Derived *dp = &d;


dp->show();
}

29
30
4. Metode virtuale
• O metodă virtuală este acea metodă care este definită cu
specificatorul virtual în clasa de bază şi apoi este
redefinită în clasele derivate:
– au implementări diferite în clasele derivate

• Redefinirea din clasele derivate are prioritate faţă de


definirea iniţială din clasa de bază, în cazul apelării
acesteia prin intermediul unui obiect instanţiat din clasa
derivată
• De obicei, o metodă virtuală defineşte o clasă generală
de acţiuni iar varianta redefinită introduce o metodă
specifică

31
• Motivaţia:
– supraîncărcarea metodelor nu este posibilă dacă semnăturile
metodelor coincid
– metodele virtuale permit acest lucru şi ca urmare prototipul
funcţiilor din clasele derivate trebuie să fie exact acelaşi ca cel
al funcţiei originale (lista de parametri şi tip returnat identice)
– dacă totuşi lista de parametri diferă şi funcţia originală este
declarată că fiind şi virtuală, mecanismul de metodă virtuală
este ignorat

• Când sunt utilizate obişnuit (apel prin obiect şi nume),


metodele virtuale se comportă la fel ca alte metode
membre ale clasei
• Comportamentul specific apare atunci când sunt
apelate printr-un pointer spre clasa de bază
• Un pointer al clasei de bază poate fi folosit pentru a
indica spre orice clasă derivată din aceasta

32
Exemplu:
class CPolygon {
protected:
int width, height;
public:
void set_w (int a) { width=a; }
void set_h (int b) { height=b; }

virtual int area(void) { return 0; }


};

class CRectangle: public CPolygon {


public:
int area (void) {
return (width * height);
}
}; 33
class CTriangle: public CPolygon {
public:
int area (void) { return (width * height / 2); }
};

int main (void)


{
CRectangle rect;
CTriangle trgl;
CPolygon poly;
CPolygon * ppoly1 = &rect, *ppoly2 = &trgl, *ppoly3 = &poly;
ppoly1->set_w (4); ppoly1->set_h (5);
ppoly2->set_w (5); ppoly2->set_h (5);
ppoly3->set_w (5); ppoly3->set_h (5);
cout << “Arie dreptunghi: ” << ppoly1->area( ) << endl;
cout << “Arie triunghi: ” << ppoly2->area( ) << endl;
cout << “Arie poligon: ” << ppoly3->area( ) << endl;
}
34
• Când un astfel de pointer indică spre un obiect derivat
ce conţine o metodă virtuală, compilatorul C++
determină care versiune a metodei va fi apelată, în
funcţie de tipul obiectului spre care indică acel pointer

• Varianta cu care se va lucra se stabileşte la apel (în


timpul execuției), motiv pentru care se foloseşte
denumirea de “legătură dinamică” (dynamic binding)
spre deosebire de “legătura statică” în care varianta cu
care se lucrează este stabilită în etapa de compilare

• Astfel se pot executa versiuni diferite ale metodei


virtuale în funcţie de tipul obiectului (o formă de
polimorfism)

35
36
Exemplu:
class baza {
public:
virtual void functie( ) {
cout << endl << "Functia din clasa de baza" << endl;
}
};

class derivat1: public baza {


public:
void functie( ) {
cout<<endl<<"Functia din prima clasa derivata"<<endl;
}
};

class derivat2: public derivat1 {


public:
void functie( ) {
cout<<endl<<"Functia din a doua clasa derivata" <<endl;
}
37
};
int main(void)
{
baza *p, ob_baza;
derivat1 ob_derivat1;
derivat2 ob_derivat2;
p = &ob_baza;
p->functie( );
p = &ob_derivat1;
p->functie( );
p = &ob_derivat2;
p->functie( );
}

38
• Alte caracteristici:
– o metodă rămâne virtuală indiferent de câte ori este moştenită
– dacă o clasă derivată nu redefineşte o metodă virtuală atunci
un obiect dintr-o clasă derivată va folosi varianta din clasa de
bază
– metodele statice nu pot fi metode virtuale
– constructorii nu pot fi metode virtuale
– destructorii pot fi metode virtuale
– metodele inline nu pot fi virtuale

39
• Incepând cu C++ 11 este posibilă împiedicarea
redefinirii unei metode virtuale, folosind specificatorul
final:

class Base
{
public:
virtual void myfunction() {//…}
};
class Derived : public Base
{
void myfunction() final { //…}
};
- Compilatorul va sancționa cu eroare orice tentativă de redefinire
într-o altă clasă derivată din clasa Derived

40
• Același specificator se poate folosi pentru a preciza că
o clasă nu mai poate fi folosită ca și clasă de bază într-
o nouă moștenire:

class Base final


{ // ...};

class Derived : public Base


{ //...};

41
5. Clase abstracte
• Sunt clase care nu sunt utilizate direct ci furnizează un
schelet pentru alte clase ce vor fi derivate din acestea
• De obicei, toate metodele membre ale unei clase
abstracte sunt virtuale şi au implementări vide urmând să
fie redefinite în clasele derivate

• O metodă virtuală ce nu are implementare se numeşte


metodă(funcţie) virtuală pură:
– doar defineşte o interfaţă pentru obiectele claselor derivate
• O metodă virtuală pură face clasa în care apare să fie o
clasă abstractă
• Metoda virtuală pură trebuie definită în clasele derivate
sau redeclarată ca metodă virtuală pură în clasele
derivate 42
• Declarare metodă virtuală pură:
virtual TipReturnat NumeFunc( ) = 0;

• O clasă abstractă nu poate fi instanţiată (nu se pot


declara obiecte având acest tip), în schimb se pot
declara pointeri sau referinţe la o clasă abstractă
– aceştia pot fi folosiţi pentru a referi obiecte ale unor clase
derivate din clasa abstractă

• Metodele virtuale pure sunt destinate moştenirii:


– definesc o interfaţă pentru o anumită caracteristică ce urmează
a fi implementată într-o clasă derivată

43
Exemplu:
class CPolygon {
protected:
int width, height;
public:
void set_w (int a) { width=a; }
void set_h (int b) { height=b; }

virtual int area (void) =0;

void print_area (void) {


cout << this->area( ) << endl;
}
};

44
class CRectangle: public CPolygon {
public:
int area (void) {
return (width * height);
}
};

class CTriangle: public CPolygon {


public:
int area (void) {
return (width * height / 2);
}
};

45
int main ( )
{
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = &rect;
CPolygon * ppoly2 = &trgl;
ppoly1->set_w (4); ppoly1->set_h (5);
ppoly2->set_w (5); ppoly2->set_h (5);
cout << "Apel direct pentru functia virtuala \n";
cout << "\tArie dreptunghi: " << ppoly1->area( ) << endl;
cout << "\tArie triunghi: " << ppoly2->area( ) << endl;
cout << “\nApel indirect pentru functia virtuala \n";
cout << "\tArie dreptunghi: " ;
ppoly1->print_area( );
cout << "\tArie triunghi: " ;
ppoly2->print_area( );
}
46

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