Prietenie și moștenire

Funcții prietene

În principiu, membrii privați și membrii protejați ai unei clase nu pot fi accesați din exteriorul clasei în care au fost declarați. Totuși, această regulă nu se aplică "prietenilor".

Prietenii sunt reprezentați de funcții sau clase declarate cu cuvântul cheie prieten.

O funcție care nu e membru poate accesat membrii privați și protejați ai unei clase dacă este declarată ca prieten al acelei clase. Aceasta se realizează incluzând în acea clasă o declarație a funcției externe și precedând-o cu cuvântul cheie prieten:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// funcții prieten
#include <iostream>
using namespace std;

class Dreptunghi {
    int latime, inaltime;
  public:
    Dreptunghi() {}
    Dreptunghi (int x, int y) : latime(x), inaltime(y) {}
    int aria() {return latime * inaltime;}
    friend Dreptunghi dubleaza (const Dreptunghi&);
};

Dreptunghi dubleaza (const Dreptunghi& param)
{
  Dreptunghi res;
  res.latime = param.latime*2;
  res.inaltime = param.inaltime*2;
  return res;
}

int main () {
  Dreptunghi foo;
  Dreptunghi bar (2,3);
  foo = dubleaza (bar);
  cout << foo.aria() << '\n';
  return 0;
}
24

Funcția dublare este un prieten al clasei Dreptunghi. De aceea, funcția dublare poate să acceseze membrii latime și inaltime (care sunt privați) ai diverselor obiecte de tip Dreptunghi. Să observăm că, deși nici în declarația funcției dubleaza nici în apelul ei ulterior din main, funcția membru dubleaza nu este considerată un membru al clasei Dreptunghi. Nu este! Ea doar are acces la membrii privați și protejați, fără a fi membru.

Situații specifice de utilizare a funcțiilor prietene sunt operațiile care au loc între două clase diferite, prin accesarea membrilor privați sau/și protejați.

Clase prieten

Similar funcțiilor prieten, o clasă prieten este o clasă ai cărei membri au acces la membrii privați sau protejați ai celeilalte clase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// clasă prieten
#include <iostream>
using namespace std;

class Patrat;

class Dreptunghi {
    int latime, inaltime;
  public:
    int aria ()
      {return (latime * inaltime);}
    void converteste (Patrat a);
};

class Patrat {
  friend class Dreptunghi;
  private:
    int latura;
  public:
    Patrat (int a) : latura(a) {}
};

void Dreptunghi::converteste (Patrat a) {
  latime = a.latura;
  inaltime = a.latura;
}
  
int main () {
  Dreptunghi drept;
  Patrat pat (4);
  drept.converteste(pat);
  cout << drept.aria();
  return 0;
}
16
În acest exemplu, clasa Dreptunghi este prieten al clasei Patrat ceea ce permite ca funcțiile membru ale lui Dreptunghi să aibă acces la membrii privați și protejați ai lui Patrat. Concret, Dreptunghi accesează variabila membru Patrat::latura, care reprezintă latura pătratului.

Altă noutate în acest exemplu: la începutul programului avem o declarație vidă a clasei Patrat. Aceasta este necesară deoarece clasa Dreptunghi folosește Patrat (ca parametru în funcția converteste), iar Patrat folosește Dreptunghi (declarând-o ca prieten).

Prieteniile nu se consideră niciodată ca fiind reciproce: în exemplul nostru, Dreptunghi este considerată ca o clasă prieten a lui Patrat, dar Patrat nu este considerată ca prieten al lui Dreptunghi. De aceea, funcțiile membru ale lui Dreptunghi pot accesa membrii privați și protejați ai lui Patrat dar nu și reciproc. Desigur, am putea să declarăm Patrat ca prieten al lui Dreptunghi, dacă ar fi necesar, permițând și accesul în sens invers.

O altă caracteristică a noțiunii de prietenie constă în faptul că nu este tranzitivă: un prieten al prietenului nu este prieten decât dacă se va preciza explicit relația de prietenie.

Moștenirea claselor

Clasles în C++ pot fi extinse, creându-se noi clase care păstrează caracteristici ale clasei de bază. Acest proces, cunoscut ca moștenire, implică o clasă de bază și o clasă derivată: Clasa derivată moștenește membrii claei de bază, iar la aceștia se pot adăuga proprii membri.

De exemplu, să ne imaginăm o serie de clase pentru a descrie două tipuri de poligoane: dreptunghiuri și triunghiuri. Aceste două tipuri de poligoane au anumite proprietăți comune, precum valorile necesare pentru calcularea ariilor: ambele pot fi descrise simplu ca înălțime și lățime (sau bază).

Am putea reprezenta în termeni de clase prin clasa Poligon, pe care să o derivăm în alte doua clase: Dreptunghi și Triunghi:


Clasa Poligon contine membri comuni pentru ambele tipuri de poligoane. În cazul nostru: latime și inaltime. Iar clasele Dreptunghi și Triunghi reprezintă clase derivate, având caracteristici și funcționalități diferite de la un tip de poligon la altul.

Clasele derivate din alte clase moștenesc toți membrii accesibili ai clasei de bază. Aceasta înseamnă că dacă o clasă de bază are un membru A și creăm o clasă derivată care are un membru B, aceasta din urmă conține atât pe A cât și pe B.

Relația de moștenire între două clase se declară în clasa derivată. Definițiile claselor derivate respectă următoarea sintaxă:

class nume_clasă_derivată: public nume_clasă_bază
{ /*...*/ };

unde nume_clasă_derivată este numele clasei derivate și nume_clasă_bază este numele clasei care stă la bază. Specificatorul de acces public poate fi înlocuit cu orice alt specificator de acces (protected sau private). Acest specificator limitează la nivelul cel mai accesibil pentru membrii moșteniți de la clasa de bază: membrii cu un nivel de accesibilitate mai mare sunt moșteniți cu acest nivel, în timp ce membrii cu un nivel de acces egal sau mai restrictiv își vor păstra restricția în clasa derivată.

// clase derivate
#include <iostream>
using namespace std;

class Poligon {
  protected:
    int latime, inaltime;
  public:
    void seteaza_valori (int a, int b)
      { latime=a; inaltime=b;}
 };

class Dreptunghi: public Poligon {
  public:
    int aria ()
      { return latime * inaltime; }
 };

class Triunghi: public Poligon {
  public:
    int aria ()
      { return latime * inaltime / 2; }
  };
  
int main () {
  Dreptunhgi drept;
  Triunghi trg;
  drept.seteaza_valori (4,5);
  trg.seteaza_valori (4,5);
  cout << drept.aria() << '\n';
  cout << trg.aria() << '\n';
  return 0;
}
20
10

Obiectele clasei Dreptunghi și Triunghi conțin membrii moșteniți de la Poligon. Aceștia sunt: latime, inaltime și seteaza_valori.

Specificatorul de acces protected folosit în clasa Poligon este similar lui private. singura diferență apare în legătură cu moștenirea: când o clasă o mostenește pe alta, membrii clasei derivate pot accesa membrii protejați moșteniți de la clasa de bază, dar nu și membrii privați.

Declarând latime și inaltime cu protected în loc de private, acești membri vor fi accesibili și pentru clasele derivate Dreptunghi și Triunghi, față de accesarea doar de catre membrii clasei Poligon. Dacă s-ar fi declarat cu public, ar fi putut fi accesați de oriunde.

Putem să sintetizăm diversele tipuri de acces în raport cu funcțiile care pot să îi acceseze astfel:

Accesspublicprotectedprivate
membrii aceleiași clasedadada
membrii claselor derivatedadanu
ne-membridanunu

unde "ne-membri" reprezintă acces de oriunde din exteriorul clasei, ca de exemplu din main, din altă clasă sau dintr-o funcție oarecare.

În exemplul de mai sus, membrii moșteniți de Dreptunghi și Triunghi au aceleași permisiuni de acces ca și cum ar fi aparținut clasei de bază Poligon:

1
2
3
4
5
Poligon::latime           // protected access
Dreptunghi::latime         // protected access

Poligon::seteaza_valori()    // public access
Dreptunghi::seteaza_valori()  // public access  

Aceasta se datorează relației de moștenire declarată prin folosirea cuvântului cheie public pentru fiecare dintre clasele derivate:

1
class Dreptunghi: public Poligon { /* ... */ }

Cuvântul cheie public de după (:) permite membrilor moșteniți de la clasa părinte (în cazul acesta Poligon) accesibilitate maximă pe care îl vor avea clasele derivate (în acest caz Dreptunghi). Deoarece cuvântul public rezervă nivelul maxim de accesibilitate, utilizarea sa permite clasei derivate să moștenească toți membrii cu același nivel ca în clasa de bază.

Cu ajutorul cuvântului cheie protected, toți membrii publici ai clasei de bază sunt moșteniți ca protected în clasa derivată. Reciproc, dacă este speficiat cel mai restrictiv nivel (private), toți membrii clasei de bază sunt moșteniți ca private și de aceea nu pot fi accesați din clasa derivată.

De exemplu, dacă fiica ar fi o clasă derivată a clasei mama, atunci am defini astfel:

1
class Fiica: protected Mama;

Aceasta ar face ca membrii clasei Fiica moșteniți de la mamă să fie setați cu nivelul de acces cel mai puțin restrictiv cu ajutorul lui protected. Adică, toți membrii cu specificatorul de acces public din Mama vor deveni protected în Fiica. Bineînțeles, aceasta nu restricționează Fiica de la a avea declarați proprii membri publici. Acel cel mai puțin restrictiv nivel de acces este setat numai pentru membrii moșteniți de la Mama.

Dacă nu este precizat niciun nivel de acces pentru moștenire, compilatorul consideră privat pentru clasele declarate cu cu cuvântul cheie class și public pentru cele declarate cu struct.

Ce se moștenește de la clasa de bază?

În principiu, o clasă derivată moștenește toți membrii clasei de bază, exceptând:

  • constructorii și destructorii
  • operatorul de atribuire a membrilor (operatorul=)
  • prietenii
  • membrii privați

Deși contructorii și destructorii clasei de bază nu sunt moșteniți drept constructori și destructori ai clasei derivate, pot fi încă apelați de către constructorul clasei derivate. Numai dacă nu este altfel precizat, constructorii clasei derivate apelează constructorii impliciți ai claselor de bază (de exemplu, constructorul fără parametri), care trebuie să existe.

Apelarea altui constructor al clasei de bază este posibilă, prin folosirea aceleiași sintaxe ca la inițializarea variabilelor membru din lista de inițializare:

nume_constructor_derivat (parameteri) : nume_constructor_bază (parameteri) {...}

De exemplu:

// constructori si clase derivate
#include <iostream>
using namespace std;

class Mama {
  public:
    Mama ()
      { cout << "Mama: fara parametri\n"; }
    Mama (int a)
      { cout << "Mama: parametru de tip int\n"; }
};

class Fiica : public Mama {
  public:
    Fiica (int a)
      { cout << "Fiica: parametru de tip int\n\n"; }
};

class Fiu : public Mama {
  public:
    Fiu (int a) : Mama (a)
      { cout << "Fiu: parametru de tip int\n\n"; }
};

int main () {
  Fiica kelly(0);
  Fiu bud(0);
  
  return 0;
}
Mama: fara parameteri
Fiica: parametru de tip int

Mama: parametru de tip int
Fiu: parameteru de tip int

Să observăm diferența între varianta cu apelarea constructorului clasei Mama la crearea unui nou obiect Fiica și varianta cu un obiect Fiu. Se datorează declarațiilor diferite de constructor pentru Fiica și Fiu:

1
2
Fiica (int a)          // nimic specificat: apel al constructorului implicit
Fiu (int a) : Mama (a)  // constructor specificat: apelul acelui constructor 

Moștenire multiplă

O clasă poate moșteni de la mai multe clase doar prin simpla precizare a mai multor clase de bază, separate prin virgulă, în lista claselor de bază (adică, după două puncte). De exemplu, dacă programul conține o anumită clasă care să afișeze pe ecran, clasă numită Iesire, și vrem ca Dreptunghi și Triunghi să o moștenească și pe ea, pe lângă clasa Poligon, vom scrie:

1
2
class Dreptunghi: public Poligon, public Iesire;
class Triunghi: public Poligon, public Iesire;

În continuare, avem exemplul complet:

// mostenire multipla
#include <iostream>
using namespace std;

class Poligon {
  protected:
    int latime, inaltime;
  public:
    Poligon (int a, int b) : latime(a), inaltime(b) {}
};

class Iesire {
  public:
    static void print (int i);
};

void Iesire::print (int i) {
  cout << i << '\n';
}

class Dreptunghi: public Poligon, public Iesire {
  public:
    Dreptunghi (int a, int b) : Poligon(a,b) {}
    int aria ()
      { return latime*inaltime; }
};

class Triunghi: public Poligon, public Iesire {
  public:
    Triunghi (int a, int b) : Poligon(a,b) {}
    int aria ()
      { return latime*inaltime/2; }
};
  
int main () {
  Dreptunghi drept (4,5);
  Triunghi trng (4,5);
  drept.print (drept.aria());
  Triunghi::print (trng.aria());
  return 0;
}
20
10  
Index
Index