Skip to content

Commit

Permalink
Merge pull request #13 from tgpetrica/add_2022-05-05
Browse files Browse the repository at this point in the history
initial commit
  • Loading branch information
tgpetrica authored May 5, 2022
2 parents 3a71502 + 3f030a5 commit a7f790d
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 1 deletion.
157 changes: 157 additions & 0 deletions POO_22_C_2022-05-05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
## Mostenire
O clasa C++ poate mosteni una sau mai multe clase.
```cpp
class A
{...};

class B: public A
{...};
```
In momentul in care dorim ca o clasa sa mosteneasca una sau mai multe clase dupa numele ei din declaratie punem `:` si o lista cu numele claselor mostenite. In fata fiecarui nume de clasa mostenita poate sa apara cuvantul rezervat `virtual` si poate aparea unul dintre specificatorii `public`, `protected` sau `private`.
```cpp
class Deriv:[virtual][public/protected/private]Baza1,...
{...};
```
Un specificator de acces pus in fata clasei mostenite restrange mai mult sau mai putin accesul la membri clasei de baza. Daca nu se pune niciun specificator de acces implicit se considera mostenire `private`. De regula, mostenirea se face insa `public`, asa cum este exemplu lui `B` mosteneste `A` (de mai sus).
Urmatorul tabel ilustreaza vizibilitatea membrilor in functie de specificatorul de acces folosit la derivare.

| clasa de baza | specificator derivare | rezultat |
| ------------- | --------------------- | -------- |
| public | public | public |
| public | protected | protected |
| public | private | protected |
| protected | ... | protected |
| private | ... | private |

```cpp
B b;
B* p1 = new B;
A* p2 = new B;
B* p3 = new A;
```
Primele doua instantieri sunt valabile pentru orice clasa (nu are legatura cu mostenirea). In prima si a doua instantiere se creeaza in memorie doua obiecte. Un obiect instanta `B` si un obiect instanta `A`. Obiectul instanta `A` poarta denumirea de **subobiect instantei B**. Din obiectul instantei `B` avem acces la toti membri `public` si `protected` din `A`. In general, cand instantiem o clasa se creeaza cate un subobiect instanta fiecarei clase mostenite direct sau indirect. A treia instantiere este posibila deoarece in urma apelului `new B` se creeaza obiect instanta `B` si subobiect instanta `A` , iar `p2` va fi pointer catre subobiect.
```cpp
delete p1;
```
La instantierea unei clase dintr-o ierarhie intai se creeaza subobiectele de sus in jos in ierarhie, iar ultimul se creeaza obiectul instanta clasei respective. Distrugerea obiectelor si subobiectelor se face in ordine inversa (de jos in sus) in ierarhie. In instantierea a doua (pentru pointerul `p1`) se creeaza subobiectul instantei `A`, apoi obiectul instantei `B`, iar la `delete p1` se sterge mai intai obiectul instantei `B`, apoi subobiectul instantei `A`.
A patra instantiere nu este posibila deoarece `p3` este pointer catre B si nu se creeaza nicio instanta `B`, atunci `p3` nu are pe cine sa retina.

## Functii virtuale
```cpp
class A
{
public:
virtual void f()
{
cout << "f din A" << endl;
}
};

class B:public A
{
void f()
{
cout << " f din B" << endl;
}
};
```
Dupa cum am mai spus, in cadrul unei ierarhii, o functie avand aceiasi semnatura (nume si parametri - ca tip si numar) poate fi imprementata diferit pe nivele diferite. Mecanismul care sta in spate poarta denumirea de **polimorfism**. Avem posibilitatea ca functia `f` sa nu o rescriem in clasa `B`. In aceasta situatie, functia `f` din `A` putea fi apelata de un obiect din `B`, fiind o clasa mostenita; am ales sa suprascriem functia `f` in `B`. Un obiect instanta `B` are posibilitatea de a apela doua functii `f`: cea din `B` si cea mostenita din `A`. Pentru a apela functia din `A` folosim operatorul de rezolutie.
```cpp
B b; //static / stack
b.f();
/*
* Apelul b.f() se restrange asupra functiei care este cea
* mai apropiata de obiectul b, adica in cazul nostru, se
* apeleaza f din B.
*/
B* p1 = new B; //dinamic / heap
p1->f();
/*
* p1->f() se restrange asupra functiei f tot din B
*/
A* p3 = new B;
p3->f();
/*
* Apelul pe->f() se restrange asupra functiei f din A, p3
* fiind pointer catre A. Din cauza ca avem clasa B
* specializata, extinsa, varianta particularizata sau
* imbunatatita ar fi mai bine ca apelul p3->f() sa se duca
* la functia f din B. Pentru a realiza acest lucru,
* declaram functia f din A ca fiind virtuala punand
* cuvantul rezervat virtual in fata functiei. Daca facem
* specificarea virtual in fata functiei f din A, apelul
* p3->f() se duce in clasa B.
```
O functie poate fi declarata virtuala numai daca este membra unei clase (adica functiile externe nu pot fi virtuale). Intr-o ierarhie, in momentul in care o functie este declarata virtuala, ea va fi virtuala si in clasele derivate. In exemplul nostru, `f` din `B` este automat virtuala.
### Mecanismul de apelare a functiilor virtuale
Adresele functiilor virtuale la compilare sunt introduse intr-o tabela speciala denumita VMT (virtual memory table). Cand este apelata o functie virtuala este cautata adresa functiei cea mai apropiata in VMT, luandu-se de acolo pointerul catre functia indentificata.
Mecanismul de apel al unei functii virtuale in consecinta mai lent decat apelul unei functii membre obisnuite nevirtuale, deoarece inainte de apelul efectiv de functie se face cautare in VMT.
Al treilea tip de instantiere devine util in urmatoarea situatie: dorim ca intr-un vector sa punem laolalta atat obiect instanta `A` cat si obiecte instanta `B`, deoarece obiectele instantei `B` sunt si obiecte de tip `A`.
De exemplu, presupuneam ca avem ierarhia:
- vehicul
- motocicleta
- autoturism
- bicicleta
Presupunem ca avem nevoie sa retinem intr-un vector mai multe vehicule, unele bicicleta, altele motocicleta si alte autoturism. Pentru a putea implementa un astfel de vector avem obligatoriu nevoie sa cream un vector de pointeri catre vehicul.
```cpp
int n;
vehicul** v;
v = new vehicul*[n];
v[0] = new bicicleta;
v[1] = new motocicleta;
v[2] = ...
```
In exemplul de mai sus, `v[i]` fiind pointer catre vehicul el retine adresa subobiectului vehicul pentru fiecare instantiere. Daca metodele din clasa vehicul le punem ca fiind virtuale orice apel de astfel de functie din `v[i]` se va duce in clasa de jos (bicicleta, motocicleta, autoturism).
```cpp
cout << v[i].nrRoti() << endl;
// se va duce intr-una dintre clasele derivate daca
// functia nrRoti este privata in functia vehicul si
// suprascrisa in clasele derivate.
```
Constructorii unei clase nu pot fi declarati virtuali, in schimb, destructorul clasei poate fi virtual.
## Destructori virtuali
Consideram din nou ierarhia `B`, `B` mosteneste `A`.
Scrierea `delete p1;` distruge ce se afla la adresa `p1` adica un obiect instanta `B` si declanseaza in sus distrugerea subobiectelor, adica, in situatia noastra, distruge subobiectul instanta `A`.
In situatia in care scriem `delete p3;` se distruge obiectul de la adresa `p3` care este, de fapt, subobiect instanta `A`, iar obiectul instanta `B` ramane in memorie, rezultand memory-leak. Pentru ca apelul `delete p3;` sa distruga si obiectul instanta `B` trebuie sa scriem desctructor in clasa `A` si sa il punem virtual, in clasa `A` adaugam `virtual~A{}`.
Apelul `delete p3;` nu mai este un apel simplu de destructor din `A`, ci se declanseaza un mecanism de identificare a destructorilor ce trebuie apelati folosind VMT.
### Apelul unui constructor dintr-o clasa derivata
Cand instantiem o clasa derivata dintr-o alta clasa, am vazut ca, o data cu obiectul creat se creeaza si cate un subobiect pentru clase mostenite direct sau indirect. Creearea oricarui obiect se face apeland la un constructor. In consecinta, atunci cand se apeleaza un constructor dintr-o clasa derivata, trebuie sa specificam cum se apeleaza cate un constructor pentru fiecare clasa mostenita direct.
In exemplul de mai sus: `B` mosteneste `A` nescriind niciun constructor in clasele `A` si `B` pentru fiecare dintre aceste clase s-a creat automat constructorul implicit fara parametri. Atat pentru obiectul creat, cat si pentru subobiect s-a apelat constructorul implicit corespunzator.
```cpp
class A
{
public:
A(){}
A(int x){cout << x << endl;}
virtual void f(){...};
virtual A{}
};
void t(int n){cout << n << endl;}
class B:public A
{
int m_b;
public:
B(){}
B(int x):A(x+1)
{
cout << x << endl;
}
void f(){...};
}
```
Pentru primul constructor al clasei `B` nespecificand apel de constructor din `A` se cauta implicit in `A` sa se apeleze constructorul fara parametri. La al doilea constructor din `B` am aratat ca in `A` se apeleaza al doilea constructor caruia ii trimite ca parametru valoarea `x+1`. De regula, parametri de apel ai constructorului din clase mostenita se specifica pentru constructorul din clasa derivata folosind parametri de apel ai constructorului din clasa derivata. In exemplul nostru, constructorul `A` a fost apelat cu valoarea `x+1` unde `x` este parametru al lui `B`.
Campul `m_b` se poate initializa ori in corpul constructorului sau in lista de apel de dupa `:` pusa dupa constructorul `B`. Tot in aceasta lista de apel pentru constructor pot fi apelate si functii.
```cpp
B b(7);
// se afiseaza:
8 //A(x-1)
6 //t(x-1)
7 //B(x)
```
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@
- pointerul ```this```
- membri statici
#### [Curs 9](https://github.com/tgpetrica/POO-courses/blob/main/POO_22_C_2022-04-21.md)
- constructorul de copiere
- constructorul de copiere
#### [Curs 10](https://github.com/tgpetrica/POO-courses/blob/main/POO_22_C_2022-05-05.md)
- mostenire
- functii virtuale
- mecanismul de apelare a functiilor virtuale
- destructori virtuali
- apelul unui constructor dintr-o clasa derivata

0 comments on commit a7f790d

Please sign in to comment.