Differences

This shows you the differences between two versions of the page.

Link to this comparison view

poo-is-ab:laboratoare:09 [2024/12/04 11:32]
razvan.cristea0106 [Vector de obiecte neomogene]
poo-is-ab:laboratoare:09 [2025/12/09 11:26] (current)
razvan.cristea0106 [Metode virtuale]
Line 1: Line 1:
-===== Laborator ​10 Vectori de obiecte neomogene ​=====+===== Laborator ​09 Clase abstracte și interfețe ​=====
  
 **Autor: Răzvan Cristea** **Autor: Răzvan Cristea**
Line 7: Line 7:
 Studentul va fi capabil la finalul acestui laborator să: Studentul va fi capabil la finalul acestui laborator să:
  
-  * recunoască ​un vector de obiecte neomogene +  * recunoască ​și să definească o metodă virtuală 
-  * știe cum să aloce și să dezaloce memoria pentru un astfel de vector +  * recunoască și să definească o metodă virtual pură 
-  * înțeleagă importanța utilizării ​acestui vector corelat cu noțiunea de suprascriere +  * înțeleagă importanța utilizării ​claselor abstracte și a interfețelor în diferite contexte ale POO 
-  * prelucreze o colecție neomogenă de date +  * înțeleagă conceptul ​de "late binding"​ corelat cu run time polymorphism 
-  * supraîncarce diverși operatori pentru clasa de bază+  * înțeleagă diferența dintre supraîncărcare ​și suprascriere
  
 ==== Introducere ==== ==== Introducere ====
  
-În acest laborator vom aprofunda utilizarea ​**claselor abstracte** și a **interfețelor**, concentrându-ne pe un aspect important al **POO** și anume gestionarea colecțiilor de date **neomogene**. Dacă până acum am lucrat cu **pointeri** pentru a manipula obiecte derivate din **clase abstracte** sau **interfețe**, acum vom explora cum putem organiza ​și gestiona aceste obiecte utilizând colecții de date, cum ar fi **vectorii**. Pentru o mai bună înțelegere a noțiunilor legate ​de **clase abstracte** și **interfețe** se recomandă citirea [[poo-is-ab:​laboratoare:​08|laboratorului 9]].+Până în prezent, am explorat conceptul de **polimorfism timpuriu (early polymorphism sau compile-time polymorphism)**, care se manifestă atunci când **funcții/​metode** cu **același nume** sunt **diferențiate prin numărul sau tipul parametrilor**. Acest lucru stă la baza conceptului de **supraîncărcare ​funcțiilor (overloading)**, care permite definirea mai multor variante ale unei funcții în cadrul **aceleiași clase**, fiecare având ​un **comportament specific** în funcție de **semnătura sa**. O altă formă importantă de **polimorfism timpuriu** este reprezentată de **template-uri** (**funcții** și **clase generice**)În acest caz, compilatorul generează automat instanțele necesare ale funcțiilor și/sau claselor, pe baza tipurilor transmise ca parametri. Acest tip de polimorfism este decis la **compilare**, ceea ce înseamnă că alegerea funcției care va fi apelată se face de către ​**compilator** pe baza tipului argumentelor oferite.
  
-==== Vector ​de obiecte neomogene ====+De exemplu, dacă avem o funcție **''​adunare''​** supraîncărcată pentru a lucra cu numere întregi și cu numere reale, **compilatorul** determină **automat** care versiune a funcției urmează să fie apelată, în funcție ​de **tipul** datelor primite ca argumente. Avantajul acestui tip de polimorfism este **viteza de execuție**,​ deoarece decizia a fost deja luată **înainte** ca programul să ruleze.
  
-Un **vector de obiecte neomogene** este o structură de date utilizată pentru a stoca și gestiona obiecte de tipuri diferitedar care împărtășesc o relație comună definită printr-o ​**clasă abstractă** sau o **interfață**. Această abordare este posibilă datorită conceptului ​de **late binding**, care permite apelarea metodelor potrivite pentru fiecare pointer ​în funcție de tipul său concret, stabilit la momentul execuției +Cu toate acestea, există cazuri în care decizia pentru metoda ce trebuie apelată ​**nu** poate fi luată de către compilatorci doar în timpul execuției. Acest procedeu este cunoscut sub denumirea de **polimorfism întârziat (late binding sau run-time polymorphism)**. Acest concept este strâns legat de **suprascrierea funcțiilor (overriding)** și de utilizarea mecanismelor ​de **moștenire** și **metode virtuale** în C++.
  
-Acest tip de vector **nu** stochează **direct** obiectele, ci **pointeri la obiecte**. Acest lucru asigură flexibilitatea de a lucra cu tipuri diferite de obiecte, cu condiția ca acestea să implementeze toate **metodele virtuale** sau **virtual pure** definite în **clasa abstractă** sau **interfața** comună.  ​+==== Overloading vs Overriding ====
  
-Avantajele utilizării unui vector de obiecte neomogene sunt: +În continuare vom prezenta un tabel care pune în evidență diferențele clare între cele două forme de polimorfism.
-  +
-   * **Polimorfism**:​ Gestionarea comportamentelor variate ale obiectelor printr-o singură interfață+
  
-   * **Scalabilitate**: Adăugarea ​de noi tipuri de obiecte devine ușoară, fără a modifica codul existent+^        ​**Overloading (Compile-Time Polymorphism)**      ​^ ​       ​**Overriding (Run-Time Polymorphism)** ​          ^ 
 +| Se aplică funcțiilor/​metodelor din **aceeași clasă, același namespace, sau același fișier** ​      | Apare în ierarhiile ​de clase (**moștenire**) ​           | 
 +| Funcțiile/​metodele au **același nume**, dar **diferă** prin **numărultipul sau ordinea parametrilor** | O metodă dintr-o clasă derivată **suprascrie** comportamentul unei **metode virtuale** din clasa de bază | 
 +| Alegerea funcției/​metodei este cută de către **compilator** pe baza semnăturii acesteia | Alegerea metodei care va fi apelată este făcută la **runtime**,​ în funcție de **tipul dinamic** al obiectului | 
 +| **Nu** necesită metode virtuale/​virtual pure                               | **Necesită** utilizarea metodelor virtuale/​virtual pure în clasa de bază | 
 +| Este o formă de polimorfism timpuriu (**early binding**) ​   | Este o formă de polimorfism întârziat (**late binding**) ​ | 
 +| Are un impact **redus** asupra performanței,​ deoarece decizia se ia la compilare | Are un impact **ușor mai mare** asupra performanței,​ deoarece decizia se ia în timpul execuției |
  
-Astfel, ​**vectorii de obiecte neomogene** reprezintă un instrument esențial în gestionarea colecțiilor de date diverse, utilizând avantajele oferite ​de **polimorfism** și de principiile ​**POO**. Această tehnică facilitează crearea ​de sisteme ​**modulare** și **extensibile**,​ esențiale pentru aplicații complexe.+**Suprascrierea** (**overriding**) implică păstrarea aceluiași antent al metodei – adică același **tip de return** și **aceeași semnătura** (numele și lista de parametri) – dar permite redefinirea completă a comportamentului acesteia într-o ​**clasă derivată**. Prin această abordare, o metodă virtuală din clasa de bază poate fi adaptată pentru a răspunde nevoilor specifice ale clasei derivate. Acest proces este esențial pentru ​**polimorfismul la timp de execuție**, oferind flexibilitate ​și posibilitatea de a extinde funcționalitățile într-un mod dinamic.
  
-Pentru a putea construi un **vector ​de obiecte neomogene** mai întâi avem nevoie de o **ierarhie de clase** iar exemplul din acest laboartor este realizat cu ajutorul claselor ​**ProdusElectronic** care este o **interfață** și respectiv **Laptop** ​și **SmartPhone** ​care sunt **clase concrete** ce **implementează** interfața anterior menționată.+Pe de altă parte, ​**supraîncărcarea** (**overloading**) presupune existența mai multor funcții/​metode cu același nume fie în cadrul aceleiași clase sau în cadrul aceluiași namespace sau în cadrul aceluiași fișier, dar care diferă prin numărul, tipul sau chiar ordinea parametrilor. Deși semnăturile ​sunt distincte, logica generală a funcțiilor/​metodelor rămâne similară, acestea fiind utilizate pentru ​oferi funcționalități variate în contexte diferite. Alegerea variantei corespunzătoare este realizată la timpul de compilare, ceea ce asigură o execuție rapidă.
  
-Interfața **ProdusElectronic** conține ​metodele ​virtual ​pure ''​**getPret**'' ​și respectiv ''​**getProducator**'' ​și un destructor ​virtual ​pur.+<note important>​Astfel **suprascrierea** permite modificarea profundă a comportamentului unei metode în cadrul unei **ierarhii de clase**, în timp ce **supraîncărcarea** oferă posibilitatea reutilizării aceluiași nume de funcție/​metodă pentru a gestiona scenarii variate, păstrând însă consistența logicii.</​note>​ 
 + 
 +==== Metode virtuale ==== 
 + 
 +Metodele ​**virtuale** reprezintă principalul mecanism prin care putem evidenția conceptul de **suprascriere (override)** în Programarea Orientată pe Obiecte. Atunci când o clasă ​conține ​**cel puțin o metodă ​virtuală**, pentru acea clasă este generată o structură denumită ​**tabelă de pointeri la funcții virtuale (vtable)**. Această tabelă stochează adresele funcțiilor virtuale definite în clasa de bază, respectiv în clasele derivate.  
 + 
 +Fiecare obiect al unei clase care derivă din clasa de bază cu metode virtuale va primi un **pointer la vtable**, ceea ce permite **legarea dinamică** a funcțiilor. Prin urmare, în momentul în care se apelează o **metodă virtuală**,​ sistemul determină în momentul execuției care implementare specifică (din clasa de bază sau din clasa derivată) trebuie utilizată, în funcție de **tipul dinamic al obiectului**. 
 + 
 +Acest mecanism stă la baza conceptului de **late binding** sau **legare dinamică**,​ care presupune că alegerea metodei ce urmează să fie executată **nu** este stabilită în timpul compilării,​ ci **în timpul execuției**,​ oferind astfel o flexibilitate sporită ​și suport pentru polimorfismul la timp de execuție. 
 + 
 +<note warning>**Late binding-ul** în cazul **metodelor virtuale** apare doar atunci când obiectele sunt accesate prin intermediul **unui pointer** sau al **unei referințe** la **clasa de bază**. Dacă un obiect derivat este manipulat direct prin **valoare**,​ are loc fenomenul **object slicing**, iar mecanismul de legare dinamică **nu** mai funcționează,​ apelul fiind rezolvat folosind tipul bazei. 
 + 
 +**Object slicing** reprezintă situația în care, la copierea unui obiect **derivat** într-o variabilă de **tipul clasei de bază**, se păstrează doar **partea de bază** a obiectului, iar atributele ​și comportamentele specifice **clasei derivate** sunt **„tăiate” (eliminate)**,​ obiectul comportându-se ca unul de **tipul clasei de bază**.</​note>​ 
 + 
 +Pentru a putea înțelege mai bine cum se poate realiza **legătura întârziată (late binding)** și cum apare fenomenul de **Object Slicing** vom propune ​un exemplu simplu de cod. Pentru a putea declara o metodă ​virtuală în C++ se folosește cuvântul cheie **virtual** care anunță compilatorul că metoda poate fi **suprascrisă** în clasele derivate și că aceasta trebuie să fie gestionată prin intermediul **vtable**.
  
 <code cpp> <code cpp>
-class ProdusElectronic+#include <​iostream>​ 
 + 
 +class Baza
 { {
 public: public:
  
- virtual ​~ProdusElectronic() = 0; // destructor virtual pur+    ​virtual ​void afisare() const // metoda virtuala 
 +    { 
 +        std::cout << "Sunt un obiect de tipul clasei Baza\n";​ 
 +    } 
 +}; 
 + 
 +class Derivata : public Baza 
 +
 +public:
  
- virtual float getPret() const = 0+    void afisare() const // aceasta functie membra suprascrie comportamentul metodei afisare din clasa parinte 
- virtual char* getProducator() const = 0;+    { 
 +        std::cout << "Sunt un obiect de tipul clasei Derivata\n"​
 +    }
 }; };
 +
 +int main()
 +{
 +    Derivata obj;
 +
 +    // 1. FARA slicing — prin pointer
 +
 +    Baza* ptr = &obj;
 +    ptr->​afisare();​ // se apeleaza metoda din clasa Derivata
 +
 +    // 2. FARA slicing — prin referinta
 +
 +    Baza& ref = obj;
 +    ref.afisare();​ // se apeleaza metoda din clasa Derivata
 +
 +    // 3. CU slicing — prin copiere (valoare)
 +
 +    Baza b = obj; // aici se produce fenomenul de Object Slicing
 +    b.afisare();​ // se apeleaza metoda din Baza (partea din clasa Derivata este "​taiata"​)
 +
 +    return 0;
 +}
 </​code>​ </​code>​
  
-<​note ​important>În C++, unui **destructor virtual pur** trebuie să îi oferim o **implementare** deoarece va fi **întotdeauna apelat** atunci când un **obiect derivat** este **distrus**. Această cerință se bazează pe mecanismul de distrugere a obiectelor, care implică apelarea destructorilor **în ordine inversă** **constructorilor**, inclusiv pentru ​clasa de bază.</​note>​+<​note>​În C++ există ​**două** tipuri de metode virtuale și anume: ​**metode virtuale** și **metode virtual pure**. Diferența între cele două tipuri constă în faptul că unei metode virtual pure **îi poate lipsi** implementarea în **clasa de bază**.</​note>​
  
-Prin urmare vom furniza o implementare pentru destructorul clasei ​**ProdusElectronic** după cum urmează în secțiunea de cod de mai jos.+În continuare propunem ca și exemplu practic clasele ​**Animal** și **Caine**, unde ne dorim să punem în evidență conceputul ​de **late binding** și să adăugăm informații noi cu privire la acestă noțiune nouă pe care o învățăm.
  
 <code cpp> <code cpp>
-ProdusElectronic::​~ProdusElectronic()+class Animal
 { {
- // chiar daca nu scriem nimic este necesar sa existe pentru a putea functiona corect dezalocarea memoriei+public: 
 + 
 +    void afisare() const; 
 +}; 
 + 
 +void Animal::​afisare() const 
 +
 +    std::cout << "Sunt un animal!\n";​
 } }
 </​code>​ </​code>​
  
-Clasele **Laptop** și **SmartPhone** pot fi observate mai jos.+Șrespectiv clasa **Caine**.
  
 <code cpp> <code cpp>
-class Laptop ​: public ​ProdusElectronic+class Caine : public ​Animal
 { {
- float pret; 
- char* producator; 
- 
 public: public:
  
- Laptop();​ +    void afisare() const;
- Laptop(const float& pret, const char* producator);​ +
- ~Laptop();​ +
- +
- float getPret() const override; +
- char* getProducator() const override;+
 }; };
 +
 +void Caine::​afisare() const
 +{
 +    std::cout << "Sunt un caine!\n";​
 +}
 </​code>​ </​code>​
 +
 +În funcția **main** vom demostra faptul că deși câinele este un animal, totuși vom observa că se vor apela metodele specifice tipurilor de date din cauza faptului că în clasa Animal metoda **''​afisare''​** nu este declarată ca fiind virtuală.
  
 <code cpp> <code cpp>
-class SmartPhone : public ProdusElectronic+int main()
 { {
- float pret+    Animal animal
- char* producator;+    ​animal.afisare()// se apeleaza afisarea din Animal
  
-public:+    Caine caine; 
 +    caine.afisare();​ // se apeleaza afisarea din Caine
  
- SmartPhone();​ +    animal = caine
- SmartPhone(const float& pret, const char* producator)+    ​animal.afisare(); // se apeleaza afisarea din Animal
- ~SmartPhone();+
  
- float getPret() const override+    Animal* pAnimal = new Animal(); 
- chargetProducator() const override+    ​pAnimal->​afisare();​ // se apeleaza afisarea din Animal 
-};+ 
 +    CainepCaine = new Caine(); 
 +    ​pCaine->​afisare()// se apeleaza afisarea din Caine 
 + 
 +    Animal* pa = pCaine; 
 +    pa->​afisare();​ // se apeleaza afisarea din Animal desi ar fi trebui sa se apeleze cea din Caine 
 + 
 +    delete pAnimal; 
 +    delete pCaine; 
 + 
 +    return 0; 
 +}
 </​code>​ </​code>​
  
-Iar implementările metodelor celor două clase se pot observa în blocurile ​de mai jos.+Soluția este să marcăm metoda ​de **''​afisare''​** ca fiind virtuală pentru a putea permite **legături întârziate**.
  
 <code cpp> <code cpp>
-Laptop::​Laptop()+class Animal
 { {
- pret = 0.0f; +public:
- producator = nullptr; +
-}+
  
-Laptop::​Laptop(const ​float& pret, const charproducator)+    virtual void afisare(const; // metoda afisare este acum virtuala ceea ce inseamna ca vom putea avea legari dinamice 
 +}; 
 +</​code>​ 
 + 
 +Iar dacă vom testa acum codul din **funcția main** vom vedea că se va produce o **legare dinamică** atunci când vom chema metoda **''​afisare''​** prin intermediul pointerului **''​pa''​**. 
 + 
 +<code cpp> 
 +int main()
 { {
- this->​pret = pret;+    Animal animal; 
 +    animal.afisare();​ // se apeleaza afisarea din Animal
  
- if (producator !nullptr+    Caine caine; 
- { +    caine.afisare(); // se apeleaza afisarea din Caine 
- this->producator ​= new char[strlen(producator+ 1]+ 
- strcpy(this->producator, producator); +    animal ​caine; 
- } +    animal.afisare(); // se apeleaza afisarea din Animal deoarece animal nu este pointer 
- else + 
- +    ​Animal* pAnimal = new Animal(); 
- this->producator = nullptr+    pAnimal->afisare(); // se apeleaza afisarea din Animal 
- }+ 
 +    Caine* pCaine ​= new Caine(); 
 +    ​pCaine->afisare(); // se apeleaza afisarea din Caine 
 + 
 +    ​Animal* pa = pCaine; 
 +    pa->afisare()// se apeleaza afisarea din Caine 
 + 
 +    delete pAnimal; 
 +    delete pCaine; 
 + 
 +    return 0;
 } }
 +</​code>​
  
-Laptop::~Laptop()+Pentru a fi și mai riguroși putem marca în **clasa derivată** metoda de **''​afisare''​** cu **override** pentru a anunța compilatorul că metoda care provine din **superclasă** urmează să fie **suprascrisă**. 
 + 
 +<code cpp> 
 +class Caine public Animal
 { {
- if (producator != nullptr) +public:
-+
- delete[] producator;​ +
-+
-}+
  
-float Laptop::​getPret() const+    void afisare() const override; // acum este mentionat explicit faptul ca metoda din Caine o va suprascrie pe cea din Animal 
 +}; 
 +</​code>​ 
 + 
 +<note important>​Cuvântul cheie **override** în **C++** este folosit pentru a specifica în mod **explicit** că o funcție membră dintr-o **clasă derivată** suprascrie o **metodă virtuală** din **clasa de bază**. Acest mecanism oferă mai multă siguranță în ceea ce privește **suprascrierea** metodelor și ajută la prevenirea **erorilor de programare**.</​note>​ 
 + 
 +==== Clase abstracte ==== 
 + 
 +În limbajul C++, o **clasă abstractă** este o clasă care conține **cel puțin** o **metodă virtuală pură**. **Metoda virtuală pură** este o funcție membră declarată în clasa de bază, dar care **nu are neapărat o implementare** în această clasă, **obligând** astfel **clasele derivate** să o **suprascrie**. O **clasă abstractă** este utilizată pentru a defini un comportament **general** care trebuie să fie specificat în **mod detaliat** în **clasele derivate**. 
 + 
 +<note warning>​O clasă abstractă **nu** poate fi folosită pentru a crea **obiecte**. Este concepută să fie doar o bază pentru alte clase care o vor **moșteni**. În schimb se pot instanția **pointeri** de tipul acestei clase care să fie inițializati cu ajutorul **claselor derivate**. Un alt aspect ce trebuie menționat este faptul că **orice** clasă derivată dintr-o clasă abstractă **trebuie** să implementeze **toate** metodele virtual pure, altfel va deveni ea însăși o **clasă abstractă**.</​note>​ 
 + 
 +O clasă abstractă poate avea membri și metode precum constructori,​ getteri, setteri și destructor care vor fi apelate în clasele derivate. În continuare vom prezenta modul în care putem pune în evidență conceptul de **late binding** transformând clasa **Animal** într-o clasă abstractă. 
 + 
 +<code cpp> 
 +class Animal
 { {
- return pret+    char* nume
-}+    int varsta;
  
-char* Laptop::​getProducator() const+public: 
 + 
 +    Animal(); 
 +    Animal(const ​char* nume, const int& varsta); 
 +    Animal(const Animal& animal); 
 +    ~Animal();​ 
 + 
 +    char* getNume() const
 +    int getVarsta() const; 
 + 
 +    virtual void afisare() const = 0; // metoda virtual pura 
 +}; 
 +</​code>​ 
 + 
 +Iar clasa **Caine** moștenește clasa **Animal** și implementează metoda virtual pură din clasa de bază. 
 + 
 +<code cpp> 
 +class Caine : public Animal
 { {
- return producator+    char* rasa; 
-}+ 
 +public: 
 + 
 +    Caine(); 
 +    Caine(const char* nume, const int& varsta, const char* rasa); 
 +    Caine(const Caine& animal); 
 +    ~Caine(); 
 + 
 +    void afisare() const override; // implementam metoda din clasa de baza 
 +};
 </​code>​ </​code>​
 +
 +Iar implementările metodelor din clasa **Caine** se pot observa mai jos.
  
 <code cpp> <code cpp>
-SmartPhone::SmartPhone()+Caine::Caine() : Animal()
 { {
- pret = 0.0f; +    rasa = nullptr;
- producator ​= nullptr;+
 } }
  
-SmartPhone::SmartPhone(const ​floatpret, const char* producator)+Caine::Caine(const ​char* nume, const intvarsta, const char* rasa) : Animal(nume,​ varsta)
 { {
- this->​pret = pret; +    ​if (rasa != nullptr) 
- +    
- if (producator ​!= nullptr) +        this->rasa = new char[strlen(rasa) + 1]; 
-+        strcpy(this->​rasarasa); 
- this->producator ​= new char[strlen(producator) + 1]; +    
- strcpy(this->​producatorproducator); +    else 
-+    
- else +        this->rasa = nullptr; 
-+    }
- this->producator ​= nullptr; +
- }+
 } }
  
-SmartPhone::~SmartPhone()+Caine::Caine(const Caine& caine) : Animal(caine)
 { {
- if (producator ​!= nullptr) +    ​if (caine.rasa ​!= nullptr) 
-+    
- delete[] producator+        rasa = new char[strlen(caine.rasa) + 1]; 
- }+        ​strcpy(rasa,​ caine.rasa);​ 
 +    } 
 +    else 
 +    { 
 +        rasa = nullptr; 
 +    ​}
 } }
  
-float SmartPhone::getPret() const+Caine::~Caine()
 { {
- return pret;+    if (rasa != nullptr) 
 +    { 
 +        delete[] rasa; 
 +    }
 } }
  
-char* SmartPhone::getProducator() const+void Caine::afisare() const
 { {
- return producator;+    std::cout << "Nume: " << (getNume() ? getNume() : "​Anonim"​) << '​\n';​ 
 +    std::cout << "​Varsta:​ " << getVarsta() << " ani\n";​ 
 +    std::cout << "Rasa: " << (rasa ? rasa : "​Necunoscuta"​) << "​\n\n"​;
 } }
 </​code>​ </​code>​
  
-Pentru a putea declara ​un **vector ​de obiecte neomogene** vom folosi un **dublu pointer** la tipul interfeței **ProdusElectronic** pe care îl vom aloca dinamic folosind **operatorul new** după cum urmează în blocul de cod de mai jos.+Iar mai jos propunem ​un exemplu ​de testare a acestor clase.
  
 <code cpp> <code cpp>
-#include "​Laptop.h"​ 
-#include "​SmartPhone.h"​ 
- 
 int main() int main()
 { {
- int nrProduse = 5+    Caine caine("​Zeus",​ 3, "​labrador"​)
- ProdusElectronic** produse = new ProdusElectronic * [nrProduse]; // vector de obiecte neomogene+    ​caine.afisare(); // valid
  
- produse[0] ​= new Laptop(5499.99f, "​HP"​); // late binding +    Animal* pAnimal ​= new Caine(); 
- produse[1] = new SmartPhone(2499.99f, "​Motorola"​);​ +    ​pAnimal->​afisare(); // valid deoarece metoda afisare este virtuala
- produse[2] = new Laptop(3000.0f,​ "​Asus"​);​ +
- produse[3] = new Laptop(7999.99f,​ "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);+
  
- for (int i = 0i < nrProduse; i++) +    delete pAnimal// incorect nu se cheama si destructorul din Caine, memory leak
-+
- std::cout << "​Pretul produsului este: " << produse[i]->​getPret() << '​\n';​ +
- std::cout << "​Numele producatorului este: " << produse[i]->​getProducator() << "​\n\n";​ +
- }+
  
- return 0;+    pAnimal = &caine; // valid datorita relatiei de tip "​is-a"​ 
 +    pAnimal->​afisare();​ // valid 
 + 
 +    ​return 0;
 } }
 </​code>​ </​code>​
  
-Tot ceea ce mai trebuie să facem acum este să dezalocăm memoria corespunzător. Dacă mai întâi ​am alocat vectorul ​și apoi am alocat câte un slot de memorie pentru fiecare pointer ​din vector ​la eliberarea memoriei vom porni în ordinea inversă după cum se poate observa în codul de mai jos.+<note warning>​În momentul de față, codul de test din **funcția main** produce un **memory leak** deoarece, în mod normal, la eliberarea memoriei, destructorii ar trebui ​să se apeleze în ordinea inversă apelării constructorilor. Dacă, în cazul nostru, ​mai întâi ​s-a apelat constructorul fără parametri pentru clasa **Animal** ​și apoi cel din clasa **Caine**, atunci, ​la **distrugerea** obiectului, se va apela destructorul din clasa **Animal** fără a-l apela și pe cel al clasei **Caine**.
  
-<code cpp> +Acest comportament apare deoarece destructorul din clasa de bază **nu** este declarat ca **metodă virtuală**În cazul **ierarhiilor de clase** care folosesc **metode virtuale** sau **metode virtual pure**, este absolut necesar ca **destructorul clasei de bază** să fie declarat **virtual**,​ astfel încât să asigure eliberarea corectă a resurselorCând destructorul este declarat **virtual**,​ el va garanta apelarea în mod **corect** a destructorilor pentru toate **clasele derivate** implicate.</​note>​
-#include "​Laptop.h" +
-#include "​SmartPhone.h"+
  
-int main()+Așadar pentru a elibera memoria **corect** vom declara destructorul clasei **Animal** ca **metodă virtuală**. 
 + 
 +<code cpp> 
 +class Animal
 { {
- int nrProduse = 5+    char* nume
- ProdusElectronic** produse = new ProdusElectronic * [nrProduse];+    int varsta;
  
- produse[0] = new Laptop(5499.99f,​ "​HP"​);​ // late binding +public:
- produse[1] = new SmartPhone(2499.99f,​ "​Motorola"​);​ +
- produse[2] = new Laptop(3000.0f,​ "​Asus"​);​ +
- produse[3] = new Laptop(7999.99f,​ "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);​+
  
- for (int i = 0; i < nrProduse; i+++    Animal(); 
- +    ​Animal(const char* nume, const int& varsta); 
- std::cout << "​Pretul produsului este: " << produse[i]->​getPret() << '​\n'​+    ​Animal(const Animal& animal); 
- std::cout << "​Numele producatorului este: " << produse[i]->​getProducator() << "​\n\n"​+    ​virtual ~Animal(); // destructor virtual
- }+
  
- for (int i = 0; i < nrProduse; i++) +    char* getNume() const; 
-+    ​int getVarsta(const;
- delete produse[i]// eliberam fiecare slot din vector +
- }+
  
- delete[] produse; // stergem vectorul +    virtual void afisare() const = 0; // metoda virtual pura 
- +};
- return 0; +
-}+
 </​code>​ </​code>​
  
-Pentru a înțelege mai bine structura unui **vector de obiecte neomogene**, este esențial ​**să vizualizăm** modul în care acesta este organizat în memorie.+Iar acum codul de test din funcția main **nu** va mai genera ​**scurgeri de memorie** fiind apelați destructorii ​în ordinea inversă apelării constructorilor. Codul complet cu implementările celor două clase poate fi descărcat de {{:​poo-is-ab:​laboratoare:​clasa_abstracta.zip|aici}}.
  
-{{ :​poo-is-ab:​laboratoare:​vector_neomogen.jpg?​direct&​600 |}}+==== Interfețe ====
  
-==== Supraîncărcarea operatorului << pentru ​o clasă abstractă ​====+În limbajul C++, o **interfață** este formă specializată de **clasă abstractă** care conține **exclusiv** metode **virtual pure** și, de regulă, un destructor **virtual pur**. Fiind o clasă destinată exclusiv definirii de funcționalități,​ o interfață **nu** conține **membri** și **nici constructori**,​ deoarece scopul său **nu** este să stocheze starea unui obiect, ci să specifice un **set de comportamente** pe care **clasele derivate** le vor implementa.
  
-În contextul exemplului prezentat anterior, ar fi foarte elegant să putem afișa elementele vectorului folosind **operatorul %%<<​%%**,​ ceea ce ar simplifica ​și uniformiza procesul de afișare. Totuși, o problemă fundamentală apare din faptul că acest operator poate fi **supraîncărcat**,​ dar nu și **suprascris**+În continuare vom prezenta un tabel cu caracteristicile interfețelor ​și ale claselor abstracte pentru ​putea identifica eventuale asemănări ​și deosebiri ​și pentru a putea înțelege când avem nevoie de fiecare în parte în practică.
  
-Prin urmare ​**operatorul %%<<​%%** nu poate fi declarat ​**virtual**, astfel încât să permită apelul unei implementări specifice ​**clasei derivate** atunci când este utilizat printr-un ​**pointer** sau o **referință** la **clasa de bază**. Soluția implică de obicei definirea unei metode virtuale ​în **clasa abstractă** și utilizarea acesteia ​în supraîncărcarea ​**operatorului %%<<​%%**+^     ​Caracteristică ​         ^     ​Interfață ​                     ^     ​Clasă abstractă ​                ^ 
 +**Metode**                  | Conține doar metode ​virtual ​pure   | Are cel puțin o metodă virtuală pură și poate include metode virtuale și metode concrete | 
 +**Membri de date**          | Nu poate avea membri ​      | Poate avea membri ​           | 
 +**Constructori**            | Nu poate avea constructori ​        | Poate avea constructori ​             | 
 +**Moștenire multiplă**      | Este utilizată pentru moștenirea multiplă, mai ales pentru a defini contracte comune | Este utilizată în ierarhii ​de clase, dar poate genera ambiguități ​în moștenirea multiplă | 
 +**Scop**                    | Definește un contract strict pentru clasele derivate | Definește un comportament parțial ​și oferă reutilizarea codului | 
 +| **Destructor** ​             | Destructorul virtual pur este obligatoriu atunci când în clasele derivate există membri de tip pointer alocați dinamic | Destructorul virtual trebuie implementat atunci când există pointeri alocați dinamic în clasele derivate | 
 +**Instanțiere**             | Nu poate fi instanțiată,​ dar se pot utiliza pointeri la tipul acesteia | Nu poate fi instanțiată,​ dar poate avea constructori pentru clase derivate |
  
-Această abordare oferă ​separare clară între logica specifică de afișare ​și mecanismul ​**operatorului %%<<​%%**, respectând în același timp principiile polimorfismului. +Ca și exemplu propunem clasa **FiguraGeometrica** care va fi interfață ce conține metodele **''​getArie''​** ​și **''​getPerimetru''​**, iar ca și clase derivate ​vom lucra cu **Cerc** și **Patrat**.
- +
-Așadar ​vom declara o metodă virtual pură în interfața ​**ProdusElectronic** și vom anunța compilatorul că vom supraîncărca și operatorul de afișare.+
  
 <code cpp> <code cpp>
-class ProdusElectronic+class FiguraGeomertica // interfata
 { {
-protected: 
- 
- virtual void afisare(std::​ostream&​ out) const = 0; // va fi folosita pentru operatorul << 
- 
 public: public:
  
- virtual ~ProdusElectronic() = 0; +    ​virtual float getArie() const = 0; 
- +    virtual ​float getPerimetru() const = 0;
- virtual float getPret() const = 0; +
- virtual ​char* getProducator() const = 0+
- +
- friend std::​ostream&​ operator<<​(std::​ostream&​ out, const ProdusElectronic* const& produsElectronic);+
 }; };
 </​code>​ </​code>​
  
-Iar implementarea operatorului de afișare este evidentă acum.+Iar în continuare vom prezenta clasele **Cerc** ​și **Patrat**.
  
 <code cpp> <code cpp>
-std::​ostream&​ operator<<​(std::​ostream&​ out, const ProdusElectronic* const& produsElectronic)+class Cerc public FiguraGeomertica
 { {
- produsElectronic->​afisare(out);​ // ne folosim de late binding +    int raza;
- return out; +
-+
-</​code>​ +
- +
-Tot ce ne mai rămâne acum de făcut este să implementăm metoda virtual pură în clasele derivate și de restul se va ocupa **late binding-ul**. +
- +
-<code cpp> +
-class Laptop : public ProdusElectronic +
-+
- float pret; +
- char* producator;​ +
- +
-protected:​ +
- +
- void afisare(std::​ostream&​ out) const override;+
  
 public: public:
  
- Laptop();​ +    Cerc(const ​intraza = 0);
- Laptop(const ​floatpret, const char* producator);​ +
- ~Laptop();+
  
- float getPret() const override; +    ​float getArie() const override; 
- char* getProducator() const override;+    float getPerimetru() const override;
 }; };
 </​code>​ </​code>​
 +
 +Iar mai jos sunt prezentate implementările metodelor aferente clasei **Cerc**.
  
 <code cpp> <code cpp>
-class SmartPhone ​public ProdusElectronic+Cerc::Cerc(const int& raza)
 { {
- float pret+    this->​raza = raza
- char* producator;+}
  
-protected:+float Cerc::getArie() const 
 +
 +    return 3.14f * raza * raza; 
 +}
  
- void afisare(std::ostream&​ out) const override;+float Cerc::getPerimetru() const 
 +
 +    return 2 * 3.14f * raza; 
 +
 +</​code>​ 
 + 
 +Declarația clasei **Patrat** se poate observa mai jos. 
 + 
 +<code cpp> 
 +class Patrat : public FiguraGeomertica 
 +
 +    int latura;
  
 public: public:
  
- SmartPhone();​ +    Patrat(const ​intlatura = 0);
- SmartPhone(const ​floatpret, const char* producator);​ +
- ~SmartPhone();+
  
- float getPret() const override; +    ​float getArie() const override; 
- char* getProducator() const override;+    float getPerimetru() const override;
 }; };
 </​code>​ </​code>​
  
-Iar implementările ​celor 2 metode se pot observa în blocurile de cod de mai jos.+Iar implementările ​metodelor sunt disponibile ​mai jos.
  
 <code cpp> <code cpp>
-void Laptop::afisare(std::​ostreamoutconst+Patrat::Patrat(const intlatura)
 { {
- out << "​Pretul laptopului este: " << pret << '​\n';​ +    this->​latura ​latura;
- out << "​Numele producatorului de laptopuri este: "; +
- +
- if (producator !nullptr) +
-+
- out << producator << '​\n';​ +
-+
- else +
-+
- out << "​N/​A\n"​; +
- }+
 } }
-</​code>​ 
  
-<code cpp> +float Patrat::getArie() const
-void SmartPhone::afisare(std::​ostream&​ out) const+
 { {
- out << "​Pretul smartphone-ului este: " << pret << '​\n'​+    return(float)latura * latura
- out << "​Numele producatorului de smartphone-uri este: ";+}
  
- if (producator != nullptr+float Patrat::​getPerimetru() const 
-+
- out << producator << '​\n'​; +    ​return(float)4 * latura;
-+
- else +
-+
- out << "​N/​A\n";​ +
- }+
 } }
 </​code>​ </​code>​
  
-Iar în funcția ​**main** vom afișa detaliile din vector folosind **operatorul %%<<​%%**.+Iar ca și exemplu de testare a funcționalităților ​în funcția main avem:
  
 <code cpp> <code cpp>
-#include "​Laptop.h"​ 
-#include "​SmartPhone.h"​ 
- 
 int main() int main()
 { {
- int nrProduse = 5; +    FiguraGeomerticapfg1 = new Cerc(4);
- ProdusElectronic** produse ​= new ProdusElectronic * [nrProduse];+
  
- produse[0] = new Laptop(5499.99f, ​"HP"); +    std::cout << ​"Aria cercului este: " ​<< pfg1->​getArie() << '​\n'​
- produse[1] = new SmartPhone(2499.99f, "​Motorola"​); +    ​std::​cout << ​"Perimetrul cercului este: " ​<< pfg1->​getPerimetru() << '​\n'​;
- produse[2] = new Laptop(3000.0f, ​"Asus"); +
- produse[3] = new Laptop(7999.99f, "​Lenovo"​);​ +
- produse[4] = new SmartPhone(3999.99f,​ "​Apple"​);+
  
- for (int i = 0; i < nrProduse; i++) +    FiguraGeomertica* pfg2 = new Patrat(5);
-+
- std::cout << produse[i] << '​\n'​// se foloseste operatorul << definit in interfata ProdusElectronic +
- }+
  
- for (int i = 0; i nrProdusei++) +    std::cout << "​\nAria patratului este: " << pfg2->​getArie(<< '​\n'​
- +    ​std::​cout << "​Perimetrul patratului este: " << pfg2->​getPerimetru() << '​\n'​;
- delete produse[i]; +
- }+
  
- delete[] produse;+    ​delete ​pfg1; 
 +    delete pfg2;
  
- return 0;+    ​return 0;
 } }
 </​code>​ </​code>​
  
-<​note ​tip>​În ​mod similar, procedăm și pentru ceilalți operatori, indiferent dacă sunt supraîncărcați ca **funcții membre** sau ca **funcții friend**. Acești operatori vor fi supraîncărcați **exclusiv** în **clasa de bază**, iar comportamentul lor specific poate fi ulterior personalizat în **clasele derivate** prin intermediul ​**suprascrierii**. +<​note ​important>​În ​cazul clasei ​**FiguraGeometrica** nu este necesară implementarea unui destructor virtual pur deoarece în clasele derivate ​**nu** există membri de tip pointer **alocați dinamic**. Prin urmare ordinea de apel a destructorilor este cea **corectă**, adică sunt apelați mai întâi destructorii claselor derivate (**Cerc** și **Patrat**) și la final va fi apelat destructorul superclasei.</​note>​
  
-Acest lucru se realizează prin definirea unor **funcții virtual pure** în clasa de bază, care stabilesc comportamentul operatorului respectiv în **subclase**. Clasele derivate vor implementa aceste **funcții virtuale**, asigurând astfel logica specifică operatorului în funcție de cerințele fiecărei clase. Această abordare permite un design flexibil și robust, bazat pe mecanismul de **run time polymorphism**.</​note>​ 
 ==== Concluzii ==== ==== Concluzii ====
  
-În acest laborator ​am aprofundat ​utilizarea claselor abstracte și a interfețelor, aplicând aceste concepte ​pentru ​înțelege și a lucra cu **vectori de obiecte neomogene**. Acest tip de colecție permite stocarea de obiecte de tipuri diferite, având la bază relația de moștenire ​și utilizând polimorfismul pentru manipularea lor unitară.+Acest laborator ​a abordat concepte avansate legate de **POO** în limbajul C++, punând accent pe mecanismele de **virtualizare**,​ diferențele dintre **overloading** și **overriding**,​ și utilizarea ​**claselor abstracte** și a **interfețelor**. 
 + 
 +== Virtualizarea în C++ == 
 + 
 +  * **Metodele virtuale** oferă flexibilitatea necesară ​pentru ​implementarea **polimorfismului dinamic**, unde comportamentul funcțiilor este stabilit ​în timpul execuției (**late binding**). 
 + 
 +  * **Metodele virtual pure** obligă clasele derivate să implementeze funcționalitățile esențiale, asigurând astfel că fiecare clasă derivată își definește comportamentul specific. 
 + 
 +== Overloading vs Overriding ==
  
-== Vectori ​de obiecte neomogene ==+  * **Overriding** este asociat cu polimorfismul dinamic (**run-time polymorphism**) și presupune redefinirea comportamentului unei metode virtuale din clasa de bază în clasele derivate.
  
-Am înțeles cum să declarăm și să populăm un vector de obiecte neomogene, folosind pointeri către clase abstracte. Acest lucru ne-a permis să lucrăm eficient cu obiecte care împărtășesc o interfață comună, dar pot avea implementări distincte. Vectorii de acest tip reprezintă o continuare firească a conceptelor de **clase abstracte** și **interfețe**, aprofundate în laboratorul anterior.+  ​* **Overloading** este asociat cu polimorfismul timpuriu (**compile-time polymorphism**) și permite mai multe metode cu același numedar cu semnături diferite.
  
-== Destructor virtual pur ==+  * Este important să folosim cuvântul cheie **override** pentru a face codul mai sigur și mai lizibil, prevenind erorile legate de semnături greșite sau lipsa suprascrierii.
  
-Am discutat ​și implementat un **destructor virtual pur**, subliniind importanța sa în ierarhiile de clase. Un destructor virtual permite apelarea corectă a destructorilor din clasele derivate în momentul eliberării memoriei pentru obiectele stocate sub forma pointerilor la clasa de bază abstractă. Prin aceasta, am evitat scurgerile de memorie și alte comportamente neașteptate.+== Clase Abstracte ​și Metode Virtual Pure ==
  
-== Supraîncărcarea operatorului ​de afișare ==+  * **Clasele abstracte** oferă un punct de plecare pentru proiectarea ierarhiilor complexe, combinând funcționalitățile comune ​și abstractizarea.
  
-Am analizat și implementat ​**supraîncărcarea operatorului %%<<​%%** pentru a simplifica afișarea elementelor din vectorul de obiecte. Acest proces a evidențiat diferența fundamentală dintre ​**supraîncărcare (overloading)** și **suprascriere (overriding)**. Deoarece operatorii nu pot fi suprascrișici doar supraîncărcați, am adaptat codul pentru a permite funcționarea acestuia cu obiecte ​de tipuri diferite.+  ​**metodă virtual pură** este o metodă **fără** implementare în clasa de bazăiar clasa devine abstractă dacă conține cel puțin o astfel ​de metodă.
  
-== Polimorfism aplicat ==+  * Destructorii virtuali sunt obligatorii în clasele abstracte pentru a asigura o eliberare corectă a resurselor în ierarhiile de moștenire.
  
-Conceptul de **late binding**, explorat anterior, a fost aplicat pentru a apela metodele obiectelor din vectorul de obiecte neomogene. Acest lucru ne-a permis să tratăm obiecte de tipuri diferite într-o manieră uniformă, demonstrând puterea polimorfismului în proiectarea și implementarea aplicațiilor scalabile.+== Interfețele în C++ ==
  
-== Continuitatea dintre laboratoare ==+  * **Interfețele** sunt un caz particular de clase abstracte care conțin **doar** metode virtual pure.
  
-Activitatea din acest laborator a consolidat noțiunile teoretice ​și practice legate de clase abstracte, interfețe ​și polimorfismoferindu-ne o bază solidă pentru a lucra cu colecții de obiecte complexe șeterogene.+  * Ele definesc **contracte stricte** între clase și sunt utilizate pentru a implementa moștenirea multiplă într-un mod clar și organizatfără ambiguități.
  
 ==== ==== ==== ====
  
-Acest laborator ​ne-demonstrat cum putem combina conceptele avansate ​de **POO** pentru a rezolva probleme realeasigurând un design robust, flexibil ​și extensibil ​al codului.+Acest laborator a evidențiat rolul important al mecanismului ​de **virtualizare**, al **claselor abstracte** ​și al **interfețelor** în proiectarea sistemelor software flexibile și extensibile. Am înțeles diferențele esențiale dintre **overloading** și **overriding** și importanța implementării corecte a **metodelor virtuale**, pentru a asigura funcționarea corectă și eficientă a aplicațiilor **orientate obiect**.
poo-is-ab/laboratoare/09.1733304739.txt.gz · Last modified: 2024/12/04 11:32 by razvan.cristea0106
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0