This is an old revision of the document!
In cadrul acestui laborator, vom aprofunda inca un concept important al Programarii Orientate pe Obiecte, functii virtuale.
Ca referinte externe, recomandam urmatorul capitol din Absolute C++:
Pentru a intelege mai bine importanta folosirii functiilor virtuale, introducem conceptul de binding(legare).
Legarea poate fi:
Legarea statica (Early binding):
Asa cum sugereaza si numele, compilatorul(sau linker-ul) asociaza in mod direct apelului de functie o adresa.
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:
In cazul apelului prin intermediul pointerilor.
tip_date_baza *obiect = new tip_date_baza; obiect->functie();
Procedeul este similar:
#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; }
In urma rularii programului, se va afisa:
In Baza
((Derivata*)bp)->afisare();
de tip clasa de baza si sa facem conversii explicite pentru a apela functia dorita
Legare dinamica/tarzie(Late binding):
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.
#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; }
Observam asadar ca de data aceasta, se va afisa
In Derivata
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?
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.
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.
Polimorfismul se poate realiza prin:
se comporta diferit in functie de context – in functie de modul in care sunt apelate – chiar daca au acelasi nume; polimorfism ad hoc
comportamente diferite la apelul unei metode, in functie de tipul lui dinamic
★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?
1. Functiile virtuale sunt folosite pentru a implementa polimorfismul in momentul rularii (” Run time Polymorphism“)
2. Ordered List ItemUn alt avantaj al functiilor virtuale este ca permit realizarea de liste neomogene de obiecte (exemplul de la finalul cursului C6)
3. Ordered List ItemDezvoltarea de biblioteci in spiritul POO.
#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(){}; };
#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; }
#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
#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; }
#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; }
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.