This shows you the differences between two versions of the page.
poo-is-ab:laboratoare:12 [2024/12/16 13:20] razvan.cristea0106 [Rezolvarea Problemei Rombului] |
poo-is-ab:laboratoare:12 [2025/01/19 22:31] (current) razvan.cristea0106 |
||
---|---|---|---|
Line 15: | Line 15: | ||
==== Introducere ==== | ==== Introducere ==== | ||
- | **Problema rombului**, cunoscută și sub numele de **diamond problem **sau **diamond of death**, apare în limbajele de programare care permit **moștenirea multiplă**, cum este cazul limbajului **C++**. Aceasta se referă la **ambiguitățile** care pot apărea atunci când o **clasă derivată** moștenește **aceeași clasă de bază** prin **două căi diferite** din ierarhia de moștenire. Rezultatul este un **conflict** între membri, metode sau alte atribute ale clasei de bază, deoarece compilatorul **nu** poate decide în **mod automat** ce instanță sau metodă să utilizeze. | + | **Problema rombului**, cunoscută și sub numele de **diamond problem **sau **diamond of death**, apare în limbajele de programare care permit **moștenirea multiplă**, cum este cazul limbajului **C++**. Aceasta se referă la **ambiguitățile** care pot apărea atunci când o **clasă derivată** moștenește **aceeași clasă de bază** prin **cel puțin două căi diferite** din ierarhia de moștenire. Rezultatul este un **conflict** între membri, metode sau alte atribute ale clasei de bază, deoarece compilatorul **nu** poate decide în **mod automat** ce instanță sau metodă să utilizeze. |
- | **Diamond problem** apare atunci când o clasă derivată moștenește două clase intermediare, iar aceste două clase intermediare moștenesc la rândul lor aceeași clasă de bază. Grafic, ierarhia de moștenire formează o structură asemănătoare unui **romb** sau **diamant**. | + | **Diamond problem** apare atunci când o clasă derivată moștenește cel puțin două clase intermediare, iar aceste clase intermediare moștenesc la rândul lor aceeași clasă de bază. Grafic, ierarhia de moștenire formează o structură asemănătoare unui **romb** sau **diamant**. Pentru a putea înțelege mai bine cum arată **diamond problem** vom ilustra mai jos cazul cel mai simplu cu 4 clase. |
{{ :poo-is-ab:laboratoare:diamond.jpg?direct&400 |}} | {{ :poo-is-ab:laboratoare:diamond.jpg?direct&400 |}} | ||
- | Așa cum se poate observa și în imaginea de mai sus clasa **D** are două copii ale clasei **A**, ceea ce duce la **ambiguități** și **inconsistențe**. De exemplu, dacă încercăm să accesăm un membru al clasei **A** din clasa **D**, compilatorul **nu** poate determina pe unde să o ia, deoarece atât calea **D-B-A** cât și **D-C-A** duc spre clasa **A**. | + | Așa cum se poate observa și în imaginea de mai sus clasa **D** are două copii ale clasei **A**, ceea ce duce la **ambiguități** și **inconsistențe**. De exemplu, dacă încercăm să accesăm un membru al clasei **A** din clasa **D**, compilatorul **nu** poate determina care este drumul corect, deoarece atât calea **D-B-A** cât și calea **D-C-A** duc spre clasa **A**. |
În limbajele **C#** și **Java**, moștenirea multiplă a claselor **nu** este permisă tocmai pentru a evita astfel de probleme. În schimb, aceste limbaje oferă mecanisme alternative, precum **interfețele**, care permit implementarea a numeroase comportamente **fără** a introduce **conflicte** legate de **ambiguitatea moștenirii**. | În limbajele **C#** și **Java**, moștenirea multiplă a claselor **nu** este permisă tocmai pentru a evita astfel de probleme. În schimb, aceste limbaje oferă mecanisme alternative, precum **interfețele**, care permit implementarea a numeroase comportamente **fără** a introduce **conflicte** legate de **ambiguitatea moștenirii**. | ||
Line 31: | Line 31: | ||
==== Rezolvarea Problemei Rombului ==== | ==== Rezolvarea Problemei Rombului ==== | ||
- | Pentru a putea gestiona și rezolva corect această problemă mai întâi trebuie să aplicăm doi pași si anume: **derivarea virtuala a claselor intermediare din clasa de baza** și respectiv **testarea aplicației**. | + | Pentru a putea gestiona și rezolva corect această problemă trebuie să aplicăm doi pași si anume: **derivarea virtuala a claselor intermediare din clasa de baza** și respectiv **apelarea constructorilor clasei de bază în lista de inițializare a constructorilor clasei nepot**. Pentru a vedea dacă aplicația se comportă în conformitate cu așteptările pe care le avem va trebui să o testăm constant pentru a vedea dacă am eliminat toate ambiguitățile generate de problema rombului. |
- | === Derivare virtuală === | + | === Derivarea virtuală === |
- | Să luam ca exemplu clasele **A**, **B**, **C**, **D**, unde **A** este clasa de bază iar **B** și **C** moștenesc clasa **A**. Clasa **D** moștenește atât clasa **B** cât și clasa **C**. Pentru fiecare clasă vom pune la dispoziție constructorul fără parametri și respectiv destructorul. | + | Să luăm ca exemplu clasele **A**, **B**, **C**, **D**, unde **A** este clasa de bază iar **B** și **C** moștenesc clasa **A**. Clasa **D** moștenește atât clasa **B** cât și clasa **C**. Pentru fiecare clasă vom pune la dispoziție constructorul fără parametri și respectiv destructorul. |
<code cpp> | <code cpp> | ||
Line 99: | Line 99: | ||
</code> | </code> | ||
- | Dacă vom inițializa în main un obiect de tipul clasei **D** vom observa că se va apela de două ori constructorul clasei **A** iar destructorul clasei **A** se va apela tot de două ori atunci când durata de viață a obiectului se încheie. | + | Dacă vom inițializa în **funcția main** un obiect de tipul clasei **D** vom observa că se va apela de două ori **constructorul** clasei **A** iar **destructorul** clasei **A** se va apela tot de două ori atunci când durata de viață a obiectului se va încheia. |
<code cpp> | <code cpp> | ||
Line 114: | Line 114: | ||
</code> | </code> | ||
- | <note warning>Comportamentul descris mai sus apare din cauza **problemei rombului**, care generează o **ambiguitate** ce conduce la **dublul apel** al constructorului și destructorului clasei de bază **A**. Această situație poate deveni problematică în special în scenariile în care superclasa **A** gestionează resurse **alocate dinamic**. În astfel de cazuri, **ambiguitatea** poate duce la comportament nedefinit, cum ar fi **memory leaks** sau chiar **crash-uri** ale aplicației, deoarece **destructorul** poate fi apelat de mai multe ori pe **aceeași resursă**.</note> | + | Iar output-ul arată ca mai jos. |
+ | |||
+ | <file> | ||
+ | Constructor A | ||
+ | Constructor B | ||
+ | Constructor A | ||
+ | Constructor C | ||
+ | Constructor D | ||
+ | |||
+ | Destructor D | ||
+ | Destructor C | ||
+ | Destructor A | ||
+ | Destructor B | ||
+ | Destructor A | ||
+ | </file> | ||
+ | |||
+ | <note warning>Comportamentul descris mai sus apare din cauza **problemei rombului**, care generează o **ambiguitate** ce conduce la **dublul apel** al constructorului și al destructorului clasei de bază **A**. Această situație poate deveni problematică în special în scenariile în care superclasa **A** gestionează resurse **alocate dinamic**. În astfel de cazuri, **ambiguitatea** poate duce la **comportament nedefinit**, cum ar fi **memory leaks** sau chiar **crash-uri** ale aplicației, deoarece **destructorul** poate fi apelat de mai multe ori pe **aceeași resursă**.</note> | ||
Rezolvarea acestei probleme este **derivarea virtuală** a claselor intermediare **B** și respectiv **C** după cum urmează. | Rezolvarea acestei probleme este **derivarea virtuală** a claselor intermediare **B** și respectiv **C** după cum urmează. | ||
Line 182: | Line 198: | ||
<note important>În lista de inițializare a constructorului clasei **D** este obligatoriu să apelăm explicit constructorul clasei **A**, deoarece în cazul **moștenirii virtuale**, **constructorii** clasei de bază **nu** mai sunt **apelați automat** prin intermediul claselor intermediare. **Derivarea virtuală** modifică lanțul de apeluri ale constructorilor, transferând responsabilitatea inițializării clasei de bază **direct** către **clasa derivată finală**. Această cerință asigură că resursele sau membrii clasei **A** sunt inițializați **corect** și **elimină ambiguitatea** în procesul de construcție a obiectelor.</note> | <note important>În lista de inițializare a constructorului clasei **D** este obligatoriu să apelăm explicit constructorul clasei **A**, deoarece în cazul **moștenirii virtuale**, **constructorii** clasei de bază **nu** mai sunt **apelați automat** prin intermediul claselor intermediare. **Derivarea virtuală** modifică lanțul de apeluri ale constructorilor, transferând responsabilitatea inițializării clasei de bază **direct** către **clasa derivată finală**. Această cerință asigură că resursele sau membrii clasei **A** sunt inițializați **corect** și **elimină ambiguitatea** în procesul de construcție a obiectelor.</note> | ||
- | Iar dacă vom testa în funcția **main** această ierarhie de clase vom vedea comportamentul așteptat. | + | Iar dacă vom testa în **funcția main** această ierarhie de clase vom obține rezultatul dorit. |
<code cpp> | <code cpp> | ||
+ | #include <iostream> | ||
+ | |||
int main() | int main() | ||
{ | { | ||
Line 214: | Line 232: | ||
<code cpp> | <code cpp> | ||
+ | #include <iostream> | ||
+ | |||
int main() | int main() | ||
{ | { | ||
Line 236: | Line 256: | ||
</file> | </file> | ||
- | Deși ordinea apelării constructorilor este cea firească în cazul destructorilor putem observa că aceștia **nu** se apelează în **ordinea inversă** constructorilor, ceea ce înseamnă că la **dezalocarea memoriei** pentru pointerul **obj** nu se produce o **legătură întârziată**. | + | Deși ordinea apelării constructorilor este cea firească în cazul destructorilor putem observa că aceștia **nu** se apelează în **ordinea inversă** constructorilor, ceea ce înseamnă că la **dezalocarea memoriei** pentru pointerul **obj** nu se produce o **legătură întârziată (late binding)**. |
- | <note warning>În cazul **problemei rombului** clasa **A** nu este o **clasă abstractă** sau o **interfață**. Toate clasele existente sunt **clase reale**, adică sunt instanțiabile, deci asigurarea legăturii întârziate se poate face doar prin intermediul **metodelor virtuale**.</note> | + | <note warning>În cazul **problemei rombului** prezentate în acest laborator clasa **A** nu este o **clasă abstractă** sau o **interfață**. Toate clasele existente sunt **clase reale**, adică sunt **instanțiabile**, deci asigurarea **legăturii întârziate** se poate face **doar** prin intermediul **metodelor virtuale**.</note> |
- | Soluția este să marcăm destructorul clasei **A** ca fiind **virtual**, astfel **tabela virtuală de pointeri** va fi moștenită de clasele **B**, **C**, **D**. | + | Soluția este să marcăm destructorul clasei **A** ca fiind **virtual**, astfel **tabela virtuală de pointeri** va fi moștenită de clasele **B**, **C** și respectiv **D**. |
<code cpp> | <code cpp> | ||
Line 272: | Line 292: | ||
Destructor A | Destructor A | ||
</file> | </file> | ||
+ | |||
+ | <note tip>Trebuie menționat însă faptul că limbajul C++ **permite** organizarea în formă de **romb** și pentru **clasele abstracte** și respectiv **interfețe**. Spre exemplu putem avea patru **clase abstracte** organizate în formă de **romb** iar **clasa de la baza rombului (clasa nepot)** este moștenită de o **clasă instanțiabilă**.</note> | ||
==== ==== | ==== ==== | ||
- | Este important să înțelegem că **problema rombului** nu reprezintă neapărat un concept negativ, ci mai degrabă o provocare specifică **moștenirii multiple**. Când apare, este esențial să o gestionăm cu atenție, pentru a evita **ambiguitățile** sau **comportamentele nedefinite** în timpul execuției programului. O gestionare corectă a acestei situații asigură un **cod robust**, **clar** și lipsit de **erori critice**, cum ar fi **memory leaks** sau conflicte în ordinea apelurilor **constructorilor** și **destructorilor**. | + | Este important să înțelegem că **problema rombului** nu reprezintă neapărat un concept negativ, ci mai degrabă o provocare specifică **moștenirii multiple**. Când apare, este esențial să o gestionăm cu atenție, pentru a evita **ambiguitățile** sau **comportamentele nedefinite** în timpul execuției programului. O gestionare corectă a acestei situații asigură un **cod robust**, **clar** și lipsit de **erori critice**, cum ar fi **memory leaks** sau **conflicte** în ordinea apelurilor **constructorilor** și respectiv **destructorilor**. |
==== Concluzii ==== | ==== Concluzii ==== | ||
Line 281: | Line 303: | ||
**Problema rombului** evidențiază complexitatea **moștenirii multiple** și potențialele **capcane** care pot apărea în **proiectarea claselor** într-un limbaj precum **C++**. Deși **moștenirea multiplă** oferă **flexibilitate** și permite **reutilizarea codului**, aceasta vine cu riscuri, cum ar fi **ambiguitatea generată de apelurile multiple ale constructorilor** sau **destructorului clasei de bază**. Soluția **derivării virtuale** este un mecanism eficient pentru a **preveni** aceste ambiguități, oferind o **modalitate clară** de a gestiona relațiile dintre clase. Totuși, utilizarea **derivării virtuale** necesită o înțelegere profundă a modului în care funcționează apelurile constructorilor și cum să definim explicit ordinea acestora. | **Problema rombului** evidențiază complexitatea **moștenirii multiple** și potențialele **capcane** care pot apărea în **proiectarea claselor** într-un limbaj precum **C++**. Deși **moștenirea multiplă** oferă **flexibilitate** și permite **reutilizarea codului**, aceasta vine cu riscuri, cum ar fi **ambiguitatea generată de apelurile multiple ale constructorilor** sau **destructorului clasei de bază**. Soluția **derivării virtuale** este un mecanism eficient pentru a **preveni** aceste ambiguități, oferind o **modalitate clară** de a gestiona relațiile dintre clase. Totuși, utilizarea **derivării virtuale** necesită o înțelegere profundă a modului în care funcționează apelurile constructorilor și cum să definim explicit ordinea acestora. | ||
- | **Diamond problem** nu este ceva rău în sine, ci o **oportunitate** de a învăța să scriem cod **bine structurat** și de a ne baza pe mecanismele oferite de limbaj pentru a rezolva **ambiguitățile**. Printr-o planificare **atentă** a ierarhiilor de clase și aplicarea **corectă** a derivării virtuale, putem evita **erori critice**, precum **memory leaks** sau **comportamente imprevizibile**. De asemenea, această problemă subliniază importanța **testării riguroase** și a **înțelegerii detaliate** a **relațiilor dintre clase** în programele complexe. | + | **Diamond problem** nu este ceva rău în sine, ci o **oportunitate** de a învăța să scriem un cod **bine structurat** și de a ne baza pe mecanismele oferite de limbajul de programare utilizat pentru a rezolva **ambiguitățile**. Printr-o planificare **atentă** a ierarhiilor de clase și aplicarea **corectă** a **derivării virtuale**, putem evita **erori critice**, precum **memory leaks** sau **comportamente imprevizibile**. De asemenea, această problemă subliniază importanța **testării riguroase** și a **înțelegerii detaliate** a **relațiilor dintre clase** în programele complexe. |