Differences

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

Link to this comparison view

poo-is:laboratoare:07 [2020/09/27 17:34]
ruben_gilian.udroiu [Polimorfism si functii virtuale]
— (current)
Line 1: Line 1:
-======== Laboratorul 07: Functii Virtuale ======== 
-In cadrul acestui laborator, vom aprofunda inca un concept important al Programarii Orientate pe Obiecte, **functii virtuale**. ​ 
- 
-Ca referinte externe, recomandam urmatorul capitol din [[https://​discourse-production.oss-cn-shanghai.aliyuncs.com/​original/​3X/​2/​3/​2380479dcb8e375425884a10da232730bbc7f88d.pdf|Absolute C++]]: 
-  * Capitolul 15 (Chapter 15: Polymorphism and Virtual Functions, pag. 661 - 693) 
- 
-====== Introducere ====== 
-<note important>​**Functiile virtuale** permit claselor derivate sa inlocuiasca implementarea metodelor din clasa de baza - **suprascriere/​supraincarcare/​override**- si pun la dispozitie mecanismul de legare dinamica.</​note>​ 
-O functie virtuala este membra a clasei de baza si este redefinita(overriden) de o clasa derivata. 
- 
-Pentru a intelege mai bine importanta folosirii functiilor virtuale, introducem conceptul de **binding**(legare). 
-<​note>​**Legarea** (**Binding**) reprezinta conectarea unui apel de functie cu functia in sine (**adresa functiei**).</​note>​ 
- 
-Legarea poate fi: 
-  * **Statica/​timpurie** (la compilare) 
-  * **Dinamica/​tarzie** (in momentul executie) 
- 
- 
-====== Legarea Statica ====== 
- 
- 
- 
-**Legarea statica** (**Early binding**): 
-  *se realizeaza la compilare (**compile time**, inainte de rularea aplicatiei) 
-  *este realizata de catre compilator si editorul de legaturi (**linker**) 
-Asa cum sugereaza si numele, compilatorul(sau linker-ul) asociaza in mod //direct// apelului de functie o adresa. 
-<note important>​Orice apel normal de functie(fara virtual)este legat static.</​note>​ 
-<​note>​**In C** toate apelurile de functii presupun realizarea unei legaturi statice.</​note>​ 
- 
-== In C++ == 
-Functiile membre ale unei clase primesc adresa obiectului care face apelul. ​ 
-  tip_date obiect; ​ 
-  obiect.functie();​ 
- 
-In functie de tipul obiectului de la acea adresa - compilatorul si editorul de legaturi stabilesc daca: 
-  *nume_functie() e membra a clasei tip_date (e declarata) 
-  *este implementata 
-  *daca este implementata,​ se face legarea statica si se poate apela acea functie 
-  *daca functia era declarata ca membra a clasei, dar nu e implementata arunca eroarea ‎‎‏‏‎ ‎‏‏‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏ ‎‏‏‎ ‎‏‎‏‏‎ ‎‏‏‎‏‏‎ ‎‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎‏‏‎ ‎**[Linker error] undefined reference to '​tip_date::​nume_functie()'​** 
- 
-In cazul apelului prin intermediul pointerilor.  ​ 
-  tip_date_baza *obiect = new tip_date_baza;​ 
-  obiect->​functie();​ 
- 
-Procedeul este similar: 
-  *compilatorul si editorul de legaturi stabilesc daca functia invocata de un pointer este de tipul acelui pointer (daca poate fi apelata) 
-  *compilatorul foloseste **tipul static al pointerului** pentru a determina daca invocarea functiei membre este legala. 
-== Exemplu == 
-<code c++ Early_Binding.cpp>​ 
-#​include<​iostream> ​ 
-using namespace std;  
-    ​ 
-class Baza 
- 
-public: ​ 
-    void afisare() { cout<<"​ In Baza \n"; }  
-};  
-    ​ 
-class Derivata: public Baza 
- 
-public: ​ 
-    void afisare() { cout<<"​In Derivata \n"; }  
-};  
-    ​ 
-int main(void) ​ 
- 
-    Baza *bp = new Derivata; ​ 
-    
-    // Compilatorul vede tipul pointerului si 
-    // apeleaza functia din clasa Baza 
-    bp->​afisare(); ​   
-  ​ 
-    return 0;  
- 
-</​code>​ 
-In urma rularii programului,​ se va afisa: 
-<​code>​ In Baza </​code>​ 
-<​note>​Daca vrem sa apelam functia de afisare pentru obiectul catre care pointeaza bp, este necesara o conversie explicita a pointerului bp din (Baza*) in (Derivata*),​ astfel incat legatura sa se faca pentru tipul de date al obiectului catre care pointeaza bp. 
-</​note>​ 
-<​code>​ ((Derivata*)bp)->​afisare();​ </​code>​ 
- 
-<note warning>​**Dar** 
-  * aceasta solutie nu e robusta 
-  * trebuie mereu sa ne punem problema catre ce tip de obiect pointeaza pointerul ​ 
-    de tip clasa de baza si sa facem conversii explicite pentru a apela functia dorita 
-  * e predispusa erorilor logice 
-</​note>​ 
- 
-<note tip>​Alternativa pusa la dispozitie in C++ este mecanismul de **legare dinamica/​tarzie (dynamic/​late binding)** - nu trebuie sa memorez catre ce tip de obiect se pointeaza in timpul 
-executiei</​note>​ 
- 
- 
-====== Legarea Dinamica ====== 
- 
-**Legare dinamica/​tarzie(Late binding):** 
-  * legarea se face dupa compilare, la rulare 
-  * in functie de tipul dinamic al pointerului (tipul obiectului catre care se pointeaza) 
-  * se poate realiza doar in contextul apelului de functii prin intermediul pointerilor 
- 
-<​note>​In acest caz, compilatorul identifica tipul obiectului la momentul rularii si apoi apeleaza functia potrivita.</​note>​ 
- 
- 
- 
-Pentru a folosi acest tip de legare, compilatorul trebuie informat ca exista posibilitate ca, la rulare, sa se doreasca apelarea unei functii de tipul dinamic al obiectului. 
-<note tip>​Pentru acest lucru vom introduce un semnal in cod -> **functii virtuale**</​note>​ 
- 
-<note important>​Legarea dinamica poate fi implementata doar in cazul limbajelor compilate! 
-</​note>​ 
- 
-== Exemplu == 
-<code c++ Late_Binding.cpp>​ 
-#​include<​iostream> ​ 
-using namespace std;  
-    ​ 
-class Baza  
- 
-public: ​ 
-    virtual void afisare() { cout<<"​ In Baza \n"; }  
-}; //legarea pentru functia afisare se face la rulare 
-    ​ 
-class Derivata: public Baza 
- 
-public: ​ 
-    void afisare() { cout<<"​In Derivata \n"; }  //​supraincarcare 
-};  //legarea pentru functia afisare se face la rulare; e virtuala 
- 
-    ​ 
-int main(void) ​ 
- 
-    Baza *bp = new Derivata; //legare dinamica (in functie de tipul dinamic), apel Derivata::​afisare() ​ 
-    bp->​afisare(); ​ 
-    return 0;  
- 
-</​code>​ 
-Observam asadar ca de data aceasta, se va afisa 
-<​code>​ In Derivata </​code>​ 
-<note important>​Legarea dinamica se poate face doar folosind **functii virtuale** si **pointeri**.</​note>​ 
- 
- 
-Dupa cum am afirmat deja, cuvantul cheie virtual informeaza compilatorul sa nu realizeze o legatura statica. ​ 
-Dar cum reuseste acesta sa puna in actiune toate mecanismele necesare realizarii de legaturi 
-dinamice? 
-<note tip>​Pentru aceasta compilatorul creeaza un tabel numit **VTABLE** pentru fiecare clasa care 
-contine macar o functie virtuala. 
- 
-In **VTABLE** sunt puse adresele functiilor virtuale ale acelei clase. 
-Fiecare obiect de tipul clasei cu functii virtuale va avea in posesie un pointer numit 
-**vpointer** (sau **VPTR**) catre adresa lui VTABLE. 
-</​note>​ 
- 
-<​note>​Crearea VTABLE –ului pentru clase si initializarea VPTR –ului este automata (facuta 
-de compilator)</​note>​ 
- 
-**Cand se face initializarea VPTR-ului?​** 
- 
-In momentul crearii unui obiect. Mai exact la apelul constructorului. 
-In cazul in care nu se implementeaza niciun constructor,​ cel generat automat face si 
-aceasta initializare. 
-<note important>​Functiile statice NU pot sa fie virtuale pentru ca functiile statice nu sunt apelate prin intermediul unui obiect si nu primesc adresa unui obiect si astfel nu au acces la VPTR.</​note>​ 
- 
- 
- 
- 
-====== Polimorfism si functii virtuale ====== 
-<​note>​“poli” – mai multe;​“morf” –forma 
-</​note>​ 
- 
-**Polimorfismul** se poate realiza prin: 
-  * **supradefinirea functiilor** - functii cu acelasi nume, dar semnaturi diferite - care se comporta diferit in functie de context – in functie de modul in care sunt apelate – chiar daca au acelasi nume; **polimorfism ad hoc** 
-  * **suprascrierea functiilor** – functii virtuale: acelasi pointer poate sa aiba comportamente diferite la apelul unei metode, in functie de tipul lui dinamic 
- 
-<​note>​**Avand in vedere importanta lor, de ce nu folosim exclusiv functii virtuale? De ce nu 
-realizeaza C++ decat legaturi dinamice?** 
- 
-★Pentru ca legarea dinamica nu este la fel de eficienta ca legarea statica (asa ca o 
-folosim doar cand e nevoie). 
- 
-**Cand folosim functii virtuale atunci?** 
- 
-★De fiecare data cand vrem ca in clasa derivata sa modificam/​adaptam/​suprascriem 
-comportamentul unei functii deja implementate in clasa de baza.  
- 
-**Cand este important acest mecanism?** 
- 
-  - Functiile virtuale sunt folosite pentru a implementa polimorfismul in momentul rularii (" Run time Polymorphism“) 
-  - Un alt avantaj al functiilor virtuale este ca permit realizarea de liste neomogene de obiecte (exemplul de la finalul cursului C6) 
-  - Dezvoltarea de biblioteci in spiritul POO. 
-</​note>​ 
- 
- 
-== Exemplu == 
-<code c++ Baza.h> 
-#pragma once 
-#include <​iostream>​ 
-using namespace std; 
-class Baza{ 
-    protected: 
-        int atr1; 
-    public: 
-        Baza(); 
-        Baza(int ); 
-        void set_atr1(int ); 
-        **virtual** void afisare(); 
-        **virtual** ~Baza(){}; 
-}; 
-</​code>​ 
- 
-<code c++ Baza.cpp>​ 
-#include "​Baza.h"​ 
-    Baza::​Baza() { } 
-    Baza::​Baza(int i):atr1(i) { } 
-    void Baza::​set_atr1(int i) {  
-        atr1 = i;  
-    } 
-    void Baza::​afisare() {  
-        cout << "atr1 = " << atr1 << endl;  
-    } 
-</​code>​ 
- 
-<code c++ Derivata.h>​ 
-#pragma once 
-#include "​Baza.h"​ 
-class Derivata : public Baza { 
-    protected: ​ 
-        int atr2; 
-    public: 
-        Derivata(); 
-        Derivata(int , int ); 
-        void set_atr2(int ); 
-        void afisare(); //afisare din Derivata e virtuala 
-};//​destructorul generat automat e virtual 
-</​code>​ 
- 
-<code c++ main.cpp>​ 
-#include "​Derivata.h"​ 
-    Derivata::​Derivata() { } 
-    Derivata::​Derivata(int a1, int a2):​Baza(a1),​atr2(a2) { } 
-    void Derivata::​set_atr2(int n) {  
-        atr2 = n;  
-    } 
-    void Derivata::​afisare() {  
-        Baza::​afisare();​ 
-        cout << "atr2 = " << atr2 << endl; 
-    } 
-</​code>​ 
- 
-<code c++ Derivata.cpp>​ 
-#include "​Derivata.h"​ 
-int main(int argc, char *argv[]) { 
-    int n; 
-    cout << "Dati dimensiunea";​ 
-    cin >> n; 
-    **Baza ****vec = new **Baza***[n];​ 
-    for (int i = 0; i < n; i++){ 
-        cout << "​Introduceti obiect de tip Baza(0) sau Derivata(1)?";​ 
-        int tip; 
-        cin >> tip; 
-        if (tip == 0){ 
-            cout << "Dati atr1:";​ 
-            int a1; 
-            cin >> a1; 
-            vec[i] = **new Baza(a1)**; 
-        } 
-        else if (tip == 1){ 
-            cout << "Dati atr1 si atr2:";​ 
-            int a1, a2; 
-            cin >> a1; cin >> a2; 
-            vec[i] = **new Derivata(a1,​a2)**;​ 
-        } else i--; 
-    }  
-    for (int i = 0; i < n; i++) 
-        **vec[i]->​afisare()**;​ 
-    //​comportament polimorf; nu mai testez eu care e tipul si nu fac conversii explicite 
-    //Baza *b = new Derivata(1,​1);​ 
-    // b->​set_atr2(2);​ 
-    //atentie, set_atr2 nu e o functie din Baza 
-    //​**ERROR:'​class Baza' has no member named '​set_atr2'​** 
-    return 0; 
-} 
-</​code>​ 
- 
-<note important>​ 
-**Recomandari** 
- 
-1. Se recomanda sa se declare ca virtuale, functiile care, in derivari 
-ulterioare NU isi schimba semnificatia (acelasi nume, semnatura, tip 
-returnat), ci doar li se modifica / adapteaza implementarea /​comportamentul 
-(in functie de noul tip de date). 
- 
-2. Daca o clasa are macar o functie virtuala, destructorul trebuie declarat 
-virtual. 
-</​note>​ 
- 
- 
- 
- 
- 
  
poo-is/laboratoare/07.1601217240.txt.gz · Last modified: 2020/09/27 17:34 by ruben_gilian.udroiu
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