Jump to content
Nytro

[C++] Functii virtuale

Recommended Posts

[C++] Functii virtuale

Ca tot aveam ceva legat de acest subiect la POO (programare orientata pe obiecte) la facultate, am decis sa fac un mic "tutorial".

Cred ca se poate intelege bine asa, prin exemplu direct.

Colorat frumos:

http://pastebin.com/NFWwf9Qv

Despre ce e vorba:

/*
Tema: Cateva lucruri despre constructori, destructori, functii virtuale, (up/down)casting in C++
Autor: Ionut Gabriel Popescu
Data: 06 Aprilie 2011
*/

#include <iostream>

using namespace std;

// O clasa de baza

class Vehicul
{
public:

// Constructor si destructor

Vehicul() { cout<<"Constructor Vehicul"<<endl; }
~Vehicul() { cout<<"Destructor Vehicul"<<endl; }

// Copiere, atribuire

Vehicul(Vehicul &ob) { cout<<"Constructor de copiere Vehicul"<<endl; }
Vehicul& operator = (Vehicul &ob) { cout<<"Operator = Vehicul"<<endl; return *this; }

// Functii pentru teste

virtual void virtuala() { cout<<"Functia virtuala() Vehicul"<<endl; }
void nevirtuala() { cout<<"Functia nevirtuala() Vehicul"<<endl; }
};

// O clasa derivata

class Masina: public Vehicul
{
public:

// Constructor si destructor

Masina() { cout<<"Constructor Masina"<<endl; }
~Masina() { cout<<"Destructor Masina"<<endl; }

// Copiere si atribuire

Masina(Masina &ob) { cout<<"Constructor de copiere Masina"<<endl; }
Masina& operator = (Masina &ob) { cout<<"Operator = Masina"<<endl; return *this; }

// Pentru teste

void virtuala() { cout<<"Functia virtuala() Masina"<<endl; }
void nevirtuala() { cout<<"Functia nevirtuala() Masina"<<endl; }

// Downcasting: cand atribuim unui obiect de clasa derivata, un obiect al clasei de baza

Masina(Vehicul &ob) { cout<<"Constructor de copiere Masina"<<endl; }
Masina& operator = (Vehicul &ob) { cout<<"Operator = Masina"<<endl; return *this; }
};

int main()
{
// Cream un obiect de tipul Vehicul si un pointer la acest tip

cout<<"-- Se creaza obiectul o_vehicul --"<<endl;
Vehicul o_vehicul, *p_vehicul;
cout<<"-- A fost creat obiectul o_vehicul --"<<endl<<endl;

// Cream apoi obiectul de tipul Masina. Se va apela mai intai constructorul clasei de baza, Vehicul, apoi constructorul clasei Masina (derivat)

cout<<"-- Se creaza obiectul o_masina --"<<endl;
Masina o_masina, *p_masina;
cout<<"-- A fost creat obiectul o_masina --"<<endl<<endl;

// Cazurile in care unui pointer la Vehicul ii atribuim adresa unui Vehicul, la fel si cu Masina nu are rost sa le iau in considerare
// Nu avem Late Binding, apelul se cunoaste de la compilare, se apeleaza normal metodele

// Testam polimorfismul de executie.
// Atribuim unui pointer la clasa de baza, adresa unui obiect derivat (sau un pointer la un obiect derivat)
// Compilatorul stie sa faca conversia (implicita) de la "derivat" la "de baza"

p_vehicul = &o_masina;

// Apelam functiile pentru teste sa vedem ce se intampla

cout<<"-- Apelam metodele pentru teste (Cazul I) --"<<endl;
p_vehicul -> virtuala();
p_vehicul -> nevirtuala();
cout<<"-- Am apelat metodele pentru teste --"<<endl;

// Dupa cum se vede, se apeleaza functia virtuala din Masina, ceea ce trebuie, deoarece am atribuit adresa unui obiect Masina pointerului
// Iar functia nevirtuala, este apelata din clasa de baza

// p_masina = p_vehicul; va genera o eroare la compilare, nu se poate face conversia
// p_vehicul = p_masina; se va executa fara nici o problema, conversii explicite

cout<<endl;

// Daca atribuim unui obiect de baza, un obiect derivat
// Se va apela operatoru = din clasa de baza, deoarece se face conversia implicita de la Masina (derivat) la Vehicul (de baza)

cout<<"-- Atribuire: de_baza = derivat --"<<endl;
o_vehicul = o_masina;
cout<<"-- Am facut atribuirea --"<<endl;

// Deci se va apela operator = din clasa de baza, Vehicul

cout<<endl;

// Dar ce se intampla daca vrem sa atribuim unui obiect derivat un obiect al clasei de baza? (downcasting)
// Pentru asta nu exista conversii implicite, dar le putem defini noi
// Si am facut asta prin definirea constructorului de copiere al clasei derivate, si al supraincarcarii lui = cu parametru de tip obiect al clasei de baza

cout<<"-- Atribuire: derivat = de_baza --"<<endl;
o_masina = o_vehicul;
cout<<"-- Am facut atribuirea --"<<endl;

// Deci se apeleaza operatorul = din Masina (derivata) cu parametru obiect de tipul clasei de baza

cout<<endl;

// Downcasting cu pointeri?
// Facem conversia explicita de la vehicul (de baza) la Masina (derivat)

p_masina = (Masina *)&o_vehicul;

// Si apelam metodele noastre sa vedem ce se intampla

cout<<"-- Apelam metodele pentru teste (Cazul II) --"<<endl;
p_masina -> virtuala();
p_masina -> nevirtuala();
cout<<"-- Am apelat metodele pentru teste --"<<endl;

// Dupa cum se vede, se apeleaza metoda virtuala din Vehicul (de baza) si metoda nevirtuala din Masina (derivat)
// Dupa cum trebuia sa stiti: daca un obiect are una sau mai multe metode virtuala, se creaza o tabela (vTable) care contine pointeri la metodele virtuale ale obiectului
// Si pentru clasa cu metode virtuale se creaza un pointer la acea tabela, ascuns programatorului, care informativ ocupa inca 4 octeti (pe sisteme de 32 de biti) pentru clasa
// Iar apelul metodelor virtuale se face prin intermediu acelui pointer. Pentru fiecare clasa ce contine 1+ metode virtuale se creaza o astfel de tabela
// Se creaza o astfel de tabela pentru clasa, nu pentru fiecare obiect in parte, deci putem avea 2000 de obiecte, vom avea o singura tabela
// Pentru clasa derivata, in acea tabela, metodele care le suprascriu (suprascriere != supraincarcare) pe cele virtuale, le inlocuiesc pe cele din clasa de baza
// Iar la apelul unei metode virtuale, este apelata metoda din acea tabela, care daca a fost suprascrisa, va fi apelata cea din clasa derivata

// Cazul I
// Sa luam primul caz: upcasting, cel clasic, cand atribuim unui pointer la o clasa de baza adresa unui obiect derivat (sau un pointer catre un obiect derivat)
// La p_vehicul = &o_masina; , pointerul p_vehicul va indica catre adresa obiectului o_masina, chiar daca este de tip Masina (derivat)
// Astfel, el va indica si catre pointerul catre vTable din clasa Masina, deci un apela catre virtuala() va apela metoda din clasa Masina (cea derivata), ceea ce ne trebuie
// Iar un apel catre nevirtuala(), dat fiind faptul ca e vorba de un pointer la Vechicul, va apela metoda nevirtuala() din clasa Vechicul

// Cazul II
// Luam acum cazul de downcasting, cand avem un pointer la un obiect derivat (p_masina) si ii atribuim, prin conversie explicita (Masina*) adresa unui obiect de baza (Vehicul)
// Astfel, p_masina este un pointer de tipul Masina si indica catre un obiect de tipul Vehicul
// Cum e de tipul Masina, apelul metodei nevirtuala() va apela metoda din clasa Masina
// Si cum acest pointer indica un obiect de tipul Vehicul, pointerul catre vTable va indica catre tabela de metode a clasei Vehicul
// Deci apelul catre metoda virtuala() va apela metoda virtuala() din clasa Vehicul

cout<<endl<<"-- Se iese din program --"<<endl;

// Sper ca ati inteles si sper ca nu am spus prostii :)

return 0;
}

Astept o parere de la cineva interesat de C++ OOP. Am incercat sa acopar cat mai multe, insa ce am acoperit e ceea ce m-a interesat pe mine, si sunt niste concepte care provoaca extrem de multe probleme in C++.

Sper sa fie de folos cuiva. :)

Link to comment
Share on other sites

Foarte misto explicat ca la prosti :). Am gasit foarte folositor polimorfismul in tratarea tuturor exceptiilor de aceeasi natura (derivate) fara sa folosesc (...) exact ca un else dupa multi else if.

Un pont: cand ai blocuri mari de comentarii incearca sa folosesti /* ... */.

Insa nu prea m-am prins de un lucru ... cand supraincarci un operator .. de ex. = si faci a = b; nu ar trebui sa fie exact ca si a.operator=(B) iar antetul functiei sa fie void operator=(MyClass obj) si atribuirea sa se faca membru cu membru (in cazul datelor ... de obicei alea private) sau ceva de genul *this = b; ?

Am vazut si ceva gen cazul ala (cand pur si simplu se returneaza this) dar nu-l inteleg daca l-ai putea explica in detaliu ar fi perfect.

Si o ultima nedumerire care o am ... nu prea am inteles de ce in cazul unor functii virtuale la definirea lor se mai adauga dupa antet un const sau un tip de date ceva.

Oricum good job :).

Link to comment
Share on other sites

Foarte misto explicat ca la prosti

E facut mai mult pentru colegii mei care nu sunt tocmai pasionati de asa ceva.

Insa nu prea m-am prins de un lucru ... cand supraincarci un operator .. de ex. = si faci a = b; nu ar trebui sa fie exact ca si a.operator=(B) iar antetul functiei sa fie void operator=(MyClass obj) si atribuirea sa se faca membru cu membru (in cazul datelor ... de obicei alea private) sau ceva de genul *this = b; ?

Cand ai Obiect a, b; si vrei sa copiezi asa: a = b? La asta cred ca te referi.

E simplu. Daca nu definesti tu constructorul de copiere Obiect(Obiect &operand) si nu supraincarci operatorul = pentru parametru de tip obiect, adica Obiect& Obiect::operator = (Obiect &), care face cam acelasi lucru, cel dintai apelandu-se la initializarea obiectului, in declaratie, atunci se vor defini cei impliciti si se va face o copiere bit cu bit, din "b" in "a".

Problema apare cand lucrezi cu pointeri. Sa zicem ca memorezi intr-o clasa un sir de caractere: char *p_sir;

La atribuirea bit cu bit, pointerul din "a" va pointa catre aceeasi adresa cu pointerul din "b". Adica se va copia adresa pointerului si ambele obiecte vor avea practic acelasi sir. Daca intre timp tu distrugi obiectul "b" si eliberezi memoria ocupata de sir, acesta nu va mai putea fi folosit nici in "a", deoarece e vorba de acelasi sir.

Solutia e ca la constructorul de copiere si la supraincarcarea lui egal cu parametru un obiect de tipul clasei curente (transmis prin referinta) sa aloci spatiu pentru sirul din "a" si sa copiezi in el sirul din "b", astfel incat sa ai 2 siruri distincte.

Aum sa trecem la ce te intereseaza pe tine. Desi pare ciudat, operatorul - returneaza o valoare. Daca nu ar returna nu ai putea folosi expresii ca: a = b = c;, mai multe expresii inlantuite.

In expresia asta, cum operatorul - se evalueaza de la dreapta la stanga, mai intai b ia valoarea c, apoi expresia b = c returneaza pe b, care e apoi copiat in a; Pentru a putea face asta cu obiecte, supraincarcarea lui = trebuie sa returneze o referina la obiectul curent.

Adica: Obiect& Obiect::operator = (Obiect &operand_2) { /* Copiere, in care pui valorile dorite in obiectul curent, pe care apoi il returnezi, pentru a putea fi folosit in continuare */ return *this; }

Adica obiectul curent, determinat de *this e primul operand, iar cel de-al doilea operand e parametrul. Copiezi din parametru in *this ce iti trebuie si returnezi *this. Pe surt, a = b returneaza a.

Si o ultima nedumerire care o am ... nu prea am inteles de ce in cazul unor functii virtuale la definirea lor se mai adauga dupa antet un const sau un tip de date ceva.

Const se foloseste, in cazul de fata, pentru a defini funtii membru constante. Ideea cu ele e simpla: nu se va permite modificarea starii obiectului din aceste functii. Se definesc pentru a se asigura de acest lucru, adaugand un "const" dupa ()-le cu parametrii functiei.

Insa cred ca tu te referi la functiile virtuale pure, care au un "= 0;" la sfarsit si nu sunt definite. Desigur, acel = 0 poate si de multe ori, si de preferat, este precedat de un "const".

E alta treaba aici. Se folosesc pentru a defini o clasa abstracta, o clasa care nu poate fi instantiata, si care obliga clasele ce o mostenesc sa implementeze acele functii.

Cred ca asta voiai sa stii, daca ai nelamuriri posteaza. :)

Edited by Nytro
  • Upvote 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...