Funcție virtuală

De la Wikipedia, enciclopedia liberă

În programarea orientată pe obiecte (POO), o funcție virtuală sau metodă virtuală este o funcție al cărei comportament, în virtutea declarării acesteia ca fiind "virtuală", este determinat de către definiția unei funcții cu aceeași semnătură cea mai îndepărtată pe linia succesorală a obiectului în care este apelată. Acest concept este o foarte importantă parte din porțiunea de polimorfism a paradigmei de programare pe obiecte (POO).

Scop[modificare | modificare sursă]

Conceptul de funcție virtuală rezolvă următoarea problemă:

În POO când o clasă derivată moștenește de la o clasă de bază, un obiect al clasei derivate poate fi considerat ca fiind (sau convertit la) o instanță a clasei de bază sau a unei clase derivate din aceasta. Dacă există funcții ale clasei de bază ce au fost redefinite în clasa derivată, apare o problemă când un obiect derivat a fost convertit la (este referit ca fiind de) tipul clasei de bază. Când un obiect derivat este considerat ca fiind de tipul clasei de bază, comportarea dorită a apelului de funcție este nedefinită.

Distincția dintre virtual (dinamic) și static este făcută pentru a rezolva această problemă. Dacă funcția în cauză este etichetată drept "virtuală" atunci funcția clasei derivate va fi apelată (dacă ea există). Dacă e statică, atunci va fi apelată funcția clasei de bază.

Exemplu[modificare | modificare sursă]

De exemplu, o clasă de bază Animal poate avea o funcție virtuală eat. Sub-clasa Fish va implementa eat() într-un mod diferit față de sub-clasa Wolf, dar poți invoca metoda eat() în cadrul oricărei instanțe de clasă de referință Animal, și obține o comportare specifică clasei derivate pentru care această metodă a fost redefinită.

Aceasta îi dă posibilitatea programatorului să proceseze o listă de obiecte din clasa Animal, spunându-i fiecăruia pe rând să mănânce (apelând funcția eat()), fără a ști ce fel de animal se poate afla pe listă. Nici măcar nu trebuie să știi cum mănâncă fiecare animal, sau care ar putea fi setul complet de tipuri posibile de animale.

Următorul este un exemplu în C++:

 #include <iostream>
 using namespace std;
 class Animal
 {
 public:
    virtual void eat() { cout << "Eu mănânc așa cum o poate face orice Animal.\n"; }
 };
 
 class Wolf : public Animal
 {
 public:
    void eat() { cout << "Eu înfulec ca un lup!\n"; }
 };
 
 class Fish : public Animal
 {
 public:
    void eat() { cout << "Eu mă hrănesc ca un pește!\n"; }
 };
 
 class OtherAnimal : public Animal
 {
 };
 
 int main()
 {
    Animal *anAnimal[4];
    anAnimal[0] = new Animal();
    anAnimal[1] = new Wolf();
    anAnimal[2] = new Fish();
    anAnimal[3] = new OtherAnimal();
 
    for(int i = 0; i < 4; i++)
        anAnimal[i]->eat();
 }

Secvență de ieșire cu metoda virtuală eat:

Eu mănânc așa cum o poate face orice Animal.
Eu înfulec ca un lup!
Eu mă hrănesc ca un pește!
Eu mănânc așa cum o poate face orice Animal.

Rezultat obținut fără metoda virtuală eat:

Eu mănânc așa cum o poate face orice Animal.
Eu mănânc așa cum o poate face orice Animal.
Eu mănânc așa cum o poate face orice Animal.
Eu mănânc așa cum o poate face orice Animal.

Clase abstracte și funcții pur virtuale[modificare | modificare sursă]

O funcție pur virtuală sau metodă pur virtuală este o funcție virtuală ce trebuie implementată de o clasă derivată ce nu este abstractă. Clasele conținând metode pur virtuale sunt denumite "abstracte"; nu poate fi creată o instanță pe baza lor în mod direct, și o clasă derivată a unei clase abstracte poate fi folosită la crearea unui obiect dacă toate metodele pur virtuale ale acelei clase au fost implementate în cadrul său sau în clasa părinte. Metodele pur virtuale de obicei au specificată o (semnătură) în secțiunea de declarații a programului și nici o definiție (implementare). Ca exemplu, o clasă abstractă de bază "MathSymbol" poate specifica o funcție pur virtuală doOperation(), și clasele derivate "Plus" și "Minus" implementează doOperation() oferind versiuni concrete. Implementarea funcției doOperation() nu ar avea sens în clasa "MathSymbol" deoarece "MathSymbol" este un concept abstract al cărui comportament este definit doar pentru fiecare tip (specie) de clasă derivată din aceasta. În mod similar, o clasă derivată din "MathSymbol" ar fi incompletă fără o implementare a metodei doOperation(). Deși metodele pur virtuale de obicei nu au o implementare în clasa în care au fost declarate, în C++ este permisă implementarea lor în chiar aceste clase, asigurând o metodă de revenire sau o comportare implicită la care se poate apela în caz de necesitate când o clasă derivată consideră oportună delegarea unei funcționalități spre clasa de bază.

Funcții pur virtuale sunt utilizate de asemenea oriunde declarațiile de metode sunt folosite pentru a defini o interfață pentru care clasele derivate vor furniza toate implementările. O clasă abstractă servind drept interfață conține doar funcții pur virtuale, și nici un membru de tip dată sau metode obișnuite. Utilizarea de clase pur abstracte drept interfețe este posibilă în C++ deoarece acesta suportă tehnica de moștenire multiplă. Deoarece multe limbaje ce suportă tehnica programării pe obiecte nu suportă și moștenirea multiplă vor furniza de obicei un mecanism de interfețe în mod separat. Lucrul acesta poate fi observat în cazul limbajului Java de exemplu.

C++[modificare | modificare sursă]

În C++, funcțiile pur virtuale sunt declarate utilizând o sintaxă specială [ = 0 ] după cum a fost demonstrat mai jos.

 class B {
   virtual void a_pure_virtual_function() = 0;
 };

Declarația funcției pur virtuale furnizează doar prototipul metodei. Deși o implementare a funcției pur virtuale în mod obișnuit nu este furnizată într-o clasă abstractă, ea poate fi totuși inclusă în aceasta. Toate clasele concrete derivate din clasa abstractă vor trebui totuși să rescrie metoda, dar implementarea oferită de clasa abstractă poate fi apelată în acest mod:

 void Abstract::pure_virtual() {
   // fă ceva
 }
 
 class Child : Abstract {
   virtual void pure_virtual(); // nu mai e abstractă, această clasă poate fi
                                // folosită la crearea de obiecte.
 };
 
 void Child::pure_virtual() {
   Abstract::pure_virtual(); // este executată implementarea din
                             // clasa abstractă
 }

Compilatorul știe ce metodă implementată va trebui apelată în timpul execuției și produce o tabelă de pointeri spre toate funcțiile virtuale dintr-o clasă, denumită o vtable sau tabelă virtuală ce va fi inclusă în programul executabil.

Destructori Virtuali[modificare | modificare sursă]

Limbajele orientate obiect gestionează alocarea și dezalocarea memoriei în mod automat când obiectele sunt create și distruse. Unele limbaje orientate obiect permit implementarea unei metode "destructor" special modificată dacă acest lucru este dorit. Un astfel de limbaj este C++, și după cum este ilustrat în exemplul următor, este important pentru o clasă de bază din C++ să conțină o metodă virtuală de tip "destructor" pentru a asigura că destructorul clasei ce a fost derivată de cele mai multe ori, va fi apelat de fiecare dată. În exemplul de mai jos lipsa unui "destructor" virtual, în timp ce este ștearsă o instanță a clasei B aceasta va apela în mod corect "destructor"-ii ambelor clase B și A; dacă este ștearsă ca o instanță a clasei B, o instanță a clasei B ștearsă via pointer al clasei de bază A va eșua în apelul destructorului pentru clasa B.

 
 #include <iostream>
 
 class A
 {
 public:
 
   A() { }
   ~A() { std::cout << "Distruge obiectul de clasă A" << std::endl; }
 };
 
 class B : public A
 {
 public:
 
   B() { }
   ~B() { std::cout << "Distruge obiectul de clasă B" << std::endl; }
 };
 
 int main()
 {
   A* b1 = new B;
   B* b2 = new B;
 
   delete b1; // Doar ~A() este apelat deși b1 este o instanță a clasei B
              // deoarece destructor-ul ~A() nu este declarat de tip virtual
   delete b2; // Apelează destructor-ii ~B() și ~A()
 
   return 0;
 }

Output:

Distruge obiectul de clasă A
Distruge obiectul de clasă B
Distruge obiectul de clasă A

Declararea în mod corect virtual ~A() a destructor-ilor pentru clasa A ca fiind de tip virtual se va asigura faptul că destructor-ul pentru clasa B este apelat în amândouă cazurile din exemplul de mai sus.

Bibliografie[modificare | modificare sursă]

Vezi și[modificare | modificare sursă]

Legături externe[modificare | modificare sursă]