Sunteți pe pagina 1din 11

7.

Funcii virtuale i polimorfism


Obiective
- nelegerea noiunii de polimorfism
- nelegerea modului n care se declar i se folosesc funciile virtuale
- nelegerea distinciei dintre clasele abstracte i clasele concrete
- Studierea modului n care se declar funciile virtuale pure pentru crearea claselor
abstracte
- nelegerea modului n care polimorfismul face sistemele software extensibile i
mai uor de ntreinut

- Introducere
Cu ajutorul funciilor virtuale i al polimorfismului este posibil proiectarea i
implementarea sistemelor software care sunt mult mai uor extensibile. Programele
pot fi concepute s proceseze n mod generic, sub forma obiectelor din clasele de
baz, a obiectelor tuturor claselor dintr-o ierarhie. Clasele care nu exist n timpul
dezvoltrii iniiale a programului pot fi adugate ulterior cu modificri minore sau
chiar fr a face modificri prii generice a programului, atta timp ct clasele sunt
pri ale ierarhiei procesate generic. Singurele pri din program care trebuie
modificate sunt cele care folosesc informaii specifice despre o clas adugat n
ierarhie.

- Instruciunile switch pentru lucrul cu diverse tipuri de


date
O variant de tratare a obiectelor care au diverse tipuri de date este folosirea
instruciunii switch prin care se pot apela aciuni diferite pentru fiecare tip de obiect.
De exemplu, ntr-o ierarhie de forme geometrice in care fiecare clas are o dat
membr care conine numele formei, o instruciune switch ar putea determina care
funcie print s fie apelat pentru un obiect particular.
Exist, ns, probleme legate folosirea instruciunii switch. Programatorul ar
putea uita, de exemplu, s testeze toate posibilele tipuri de dat care exist n
ierarhia de clase. Dup adugarea unei noi clase n ierarhie, programatorul ar putea
uita s adauge i aceast clas n lista de cazuri switch. Fiecare adugare sau
tergere a unei clase nseamn o modificare a instruciunii switch care este o
potenial surs de erori.
Funciile virtuale i programarea polimorfic pot elimina necesitatea logicii
switch. Programatorul poate folosi mecanismul funciilor virtuale pentru a automatiza
implementarea acestei logici.

- Funciile virtuale
S presupunem c avem mai multe clase care reprezint forme geometrice:
Point, Circle, Triangle, Rectangle, Square derivate din clasa de baz
Shape. Fiecare dintre aceste clase trebuie s permit afiarea numelui formei pe
care o reprezint. Dei fiecare clas are o funcie printShapeName, implementarea
funciei printShapeName pentru fiecare form este diferit. Ar fi de dorit ca toate
formele s poat fi tratate generic, fiind derivate din clasa de baz Shape. Prin acest
mecanism, pentru oricare dintre forme s-ar apela funcia printShapeName din clasa

Programarea calculatoarelor i limbaje de programare II

de baz Shape, urmnd ca, dup tipul de dat al obiectului, programul s determine
dinamic (n timpul execuiei) care dintre funciile printShapeName din clasele
derivate se folosete.
Pentru a permite acest lucru, funcia printShapeName trebuie declarat virtual
n clasa de baz, iar fiecare clas din ierarhia de derivare trebuie s o suprascrie
pentru ca s implementeze un comportament particular.
Declararea unei funcii virtuale se face prin adugarea cuvntului cheie
virtual naintea prototipului n clasa de baz:
virtual void printShapeName() const;
Acest prototip de funcie este cel care trebuie declarat in clasa de baz Shape.
Exemplu
#include <iostream>
using namespace std;
class Shape{
public:
virtual void printShapeName() const
{
cout << "Shape::printShapeName()" << endl;
}
void init() const
{
cout << "Shape::init()" << endl;
}
};
class Point : public Shape{
public:
void printShapeName() const
{
cout << "Point::printShapeName()" << endl;
}
void init() const
{
cout << "Point::init()" << endl;
}
};
int main()
{
cout << "Functii apelate prin pointer la Shape:" << endl;
Shape* shapePtr = new Shape();
shapePtr->printShapeName();
shapePtr->init();
cout << "\nFunctii apelate prin pointer la Shape "
<< "initializat prin pointer la Point:" << endl;
Point* pointPtr = new Point();
shapePtr = pointPtr;
cout << "Comportament polimorfic: ";
shapePtr->printShapeName();
shapePtr->init();

Programarea calculatoarelor i limbaje de programare II

cout << "\nFunctii apelate prin obiect de tip Shape:"


<< endl;
Shape shape;
shape.printShapeName();
shape.init();
cout << "\nFunctii apelate prin obiect de tip Point:"
<< endl;
Point point;
point.printShapeName();
point.init();
cout << "\nFunctie non-virtuala apelata prin pointer la "
<< "Shape:" << endl;
shapePtr = &point;
cout << "Comportament non-polimorfic: ";
shapePtr->init();
return 0;
}
Rulnd acest program obinem urmtorul rezultat:
Functii apelate prin pointer la Shape:
Shape::printShapeName()
Shape::init()
Functii apelate prin pointer la Shape initializat
pointer la Point:
Comportament polimorfic: Point::printShapeName()
Shape::init()

prin

Functii apelate prin obiect de tip Shape:


Shape::printShapeName()
Shape::init()
Functii apelate prin obiect de tip Point:
Point::printShapeName()
Point::init()
Functie non-virtuala apelata prin pointer la Shape:
Comportament non-polimorfic: Shape::init()
Funcia printShapeName din clasa de baz poate fi invocat printr-un pointer sau o
referin la clasa de baz:
Shape* shapePtr = new Shape();
shapePtr->printShapeName();
Iniializnd pointerul la clasa de baz cu un pointer la o clasa derivat, programul va
alege dinamic (n timpul rulrii) implementarea funciei printShapeName din clasa
derivat bazndu-se pe tipul obiectului apelant:
Point* pointPtr = new Point();
shapePtr = pointPtr;
3

Programarea calculatoarelor i limbaje de programare II

shapePtr->printShapeName();
Acest proces se numete legare dinamic (dynamic binding).
Cnd o funcie virtual este apelat referind un obiect prin nume i selecia
funciei membre prin operatorul punct ., referina este rezolvat la compilare (static
binding), iar funcia virtual apelat este cea definit pentru clasa creia i aparine
obiectul:
Point point;
point.printShapeName();

- Clase de baz abstracte i clase de baz concrete


Cnd ne gndim la o clas ca la un tip de dat, ne ateptm ca n aplicaiile
care folosesc acea clas s fie instaniate obiecte ale sale. Exist, totui, cazuri n
care este util s se declare clase pentru care programatorul nu are intenia s
instanieze obiecte. Acestea sunt clase abstracte. Deoarece acestea sunt folosite
drept clase de baz n ierarhii de motenire, obinuim s le numim clase de baz
abstracte. Aadar, nu pot fi instaniate obiecte din clasele de baz abstracte.
Rolul unei clase abstracte este acela de a crea o clas de baz din care alte
clase pot moteni interfaa sau implementarea. Clasele din care pot fi instaniate
obiecte sunt clase concrete.
Clas abstract de baz
Clase derivate
FormaBidimensionala
Cerc
Triunghi
Dreptunghi
FormaTridimensionala Cub
Sfera
Cilindru
Clasele de baz abstracte sunt de regul prea generice pentru a defini obiecte
reale. Este nevoie de clase mai specifice pentru a putea justifica posiblitatea de a
instania obiecte.
O clas devine abstract dac una sau mai multe funcii virtuale este declarat
pur. O funcie virtual devine pur dac declaraia sa este urmat de =0:
virtual void earnings() const = 0; //functie virtuala pura
Dac o clas este derivat dintr-o clas de baz care conine o funcie virtual
pur i nu definete acea funcie, atunci funcia este pur i n clasa derivat, iar
clasa derivat este i ea abstract.
O ierarhie de clase nu trebuie s conin neaprat clase abstracte. Atunci cnd
se ntmpl, ns, acest lucru, clasele abstracte se gsesc n vrful ierarhiei, iar la
baza ierarhiei sunt clase concrete.

- Polimorfismul
Limbajul C++ ofer suport pentru polimorfism care nseamn posibilitatea ca
obiecte din diverse clase care sunt legate prin relaii de motenire s rspund diferit
la acelai mesaj, adic la acelai apel de funcie.
Polimorfismul este implementat prin funciile virtuale. Atunci cnd programul
cere folosirea unei funcii printr-un pointer sau o referin la o clasa de baz, C++
alege suprascrierea corect din clasa derivat corespunztoare obiectului care o
apeleaz.
Uneori, o funcie non-virtual este definit n clasa de baz i suprascris ntr-o
clas derivat. Dac o astfel de funcie membr este apelat printr-un pointer la
clasa de baz iniializat cu adresa unui obiect al unei clase derivate, este apelat
4

Programarea calculatoarelor i limbaje de programare II

versiunea din clasa de baz. Dac funcia membr este apelat printr-un pointer la
clasa derivat, este folosit versiunea funciei din clasa derivat. Acesta este un
comportament non-polimorfic.
Polimorfismul promoveaz extensibilitatea. Aplicaiile software scrie n manier
polimorfic sunt independente de tipurile obiectelor ctre care se trimit mesaje.
Astfel, noile tipuri de obiecte care rspund la mesajele existente pot fi adugate ntrun astfel de sistem fr a modifica sistemul de baz. Atunci cnd codul client
intaniaz, ns, obiecte noi, programul trebuie recompilat.
Cu toate c nu se pot instania obiecte din clasele de baz abstracte, se pot
declara pointeri sau referine la aceste clase. Aceti pointeri sau referine pot fi
utilizai pentru a permite manipulrile polimorfice ale obiectelor din clasele derivate
cnd se instaniaz astfel de obiecte din clase concrete.
Polimorfismul i funciile virtuale sunt utile i atunci cnd, ntr-o faz
intermediar a proiectrii i implementrii unei aplicaii software, nu sunt cunoscute
toate clasele care vor fi folosite n versiunea final. Noile clase care sunt adugate
sistemului sunt integrate prin legarea dinamic (dynamic binding, numit uneori i late
binding). Tipul unui obiect care apeleaz o funcie virtual nu este nevoie s fie
cunoscut la compilare. La rulare, funcia apelat virtual este identificat cu funcia
membr din clasa creia i aparine obiectul.

- Destructorii virtuali
Atunci cnd se folosete polimorfismul pentru procesarea obiectelor alocate
dinamic dintr-o ierarhie de clase apar probleme legate de distrugerea acestora. Dac
un obiect cu un destructor non-virtual este distrus explicit prin aplicarea
operatorului delete unui pointer la clasa de baz care pointeaz ctre obiect, se
apeleaz destructorul clasei de baz, indiferent de tipul actual al obiectului. O soluie
simpl la aceast problem este declararea virtual a destructorului clasei de
baz. Aceast declarare face ca toi destructorii claselor derivate s devin virtuali,
chiar dac, evident, au nume diferite de cel al destructorului clasei de baz. Dac
acum un obiect din ierarhie este distrus explicit prin aplicarea operatorului delete unui
pointer ctre clasa de baz care a fost iniializat cu adresa unui obiect dintr-o clas
derivat, se apeleaz, n mod corect, destructorul clasei derivate corespunztoare
pointerului cu care a fost iniializat pointerul la clasa de baz.
Spre deosebire de destructori, constructorii nu pot fi declarai virtual.

- Studiu de caz: Motenirea interfeei i a implementrii


Vom rescrie ierarhia de clase care cuprinde tipurile Point, Circle i
Cylinder prin adugarea, n vrful ierarhiei, a clasei de baz Shape care are dou
funcii virtuale pure: printShapeName i print, de aceea Shape este o clas de
baz abstract. Clasa Shape conine nc dou funcii virtuale, area i volume,
fiecare dintre ele cu cte o implementare implicit care returneaz valoarea 0. Clasa
Point este derivat din clasa Shape. Interpretarea dat valorii funciilor area i
volume este, astfel, corect. Clasa Circle motenete funcia volume de la clasa
Point, dar are propria implementare pentru funcia area. Clasa Cylinder are
propriile implementri att pentru funcia area, ct i pentru funcia volume.
Dei clasa Shape este o clas abstract, ea conine implementri ale unor
funcii membre i aceste implementri sunt motenite de clasele derivate. Clasa
Shape are o interfa sub forma a patru funcii virtuale care este motenit de clasele
derivate. Aceste funcii pot fi utilizate de toate clasele din ierarhia de motenire.
5

Programarea calculatoarelor i limbaje de programare II

Exemplu
shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
public:
virtual double area() const {return 0.0;}
virtual double volume() const {return 0.0;}
//functii virtuale pure suprascrise in clasele derivate
virtual void printShapeName() const = 0;
virtual void print() const = 0;
};
#endif
point1.h
#ifndef POINT1_H
#define POINT1_H
#include <iostream>
using std::cout;
#include "shape.h"
class Point : public Shape
{
public:
Point(int = 0, int = 0); //constructor implicit
void setPoint(int, int); //seteaza coordonatele
int getX() const { return x; } //returneaza x
int getY() const { return y; } //returneaza y
virtual void printShapeName() const {cout << "Point: ";}
virtual void print() const;
private:
int x, y; //x si y coordonatele unui punct
};
#endif
point1.cpp
#include <iostream>
#include "point1.h"
Point::Point(int a, int b)
{ setPoint(a, b); }
void Point::setPoint(int a, int b)
{
x = a;
y = b;
}

Programarea calculatoarelor i limbaje de programare II

void Point::print() const


{ cout << '[' << x << ", " << y << ']'; }
circle1.h
#ifndef CIRCLE1_H
#define CIRCLE1_H
#include "point1.h"
class Circle : public Point
{
public:
//constructor implicit
Circle(double r = 0.0, int x = 0, int y = 0);
void setRadius(double); //seteaza radius
double getRadius() const; //intoarce radius
virtual double area() const; //calculeaza aria
virtual void printShapeName() const {cout << "Circle: ";}
virtual void print() const;
private:
double radius;
};
#endif
circle1.cpp
#include <iostream>
using std::cout;
#include "circle1.h"
Circle::Circle(double r, int a, int b)
: Point(a, b)
{ setRadius(r); }
void Circle::setRadius(double r)
{ radius = (r > 0 ? r : 0); }
double Circle::getRadius() const
{ return radius; }
double Circle::area() const
{ return 3.14159 * radius * radius; }
void Circle::print() const
{
Point::print();
cout << "; Raza = " << radius;
}
cylinder1.h
#ifndef CYLINDER1_H
#define CYLINDER1_H
#include "circle1.h"
class Cylinder : public Circle
{
public:
7

Programarea calculatoarelor i limbaje de programare II

//constructor implicit
Cylinder(double h = 0.0, double r = 0.0,
int x = 0, int y = 0);
void setHeight(double);
double getHeight() const;
virtual double area() const;
virtual double volume() const;
virtual void printShapeName() const {cout << "Cylinder: ";}
virtual void print() const;
private:
double height;
};
#endif
cylinder1.cpp
#include <iostream>
using std::cout;
#include "cylinder1.h"
Cylinder::Cylinder(double h, double r, int x, int y)
: Circle(r, x, y)
{ setHeight(h); }
void Cylinder::setHeight(double h)
{ height = (h >= 0 ? h : 0); }
double Cylinder::getHeight() const
{ return height; }
double Cylinder::area() const
{
return 2 * Circle::area() +
2 * 3.14159 * radius * height;
}
double Cylinder::volume() const
{ return Circle::area() * height; }
void Cylinder::print() const
{
Circle::print();
cout << "; Inaltimea = " << height;
}
test_shape.cpp
#include<iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;

Programarea calculatoarelor i limbaje de programare II

#include "shape.h"
#include"point1.h"
#include "circle1.h"
#include "cylinder1.h"
void virtualViaPointer(const Shape*);
void virtualViaReference(const Shape&);
int main()
{
cout << setiosflags(ios::fixed | ios::showpoint)
<< setprecision(2);
Point point(7, 11);
Circle circle(3.5, 22, 8);
Cylinder cylinder(10, 3.3, 10, 10);
point.printShapeName();
point.print();
cout << '\n';

//legare statica (static binding)


//legare statica

circle.printShapeName();
circle.print();
cout << '\n';

//legare statica
//legare statica

cylinder.printShapeName(); //legare statica


cylinder.print();
//legare statica
cout << '\n';
//tablou de pointeri la clasa de baza
Shape *arrayOfShapes[3];
//arrayOfShapes[0] pointeaza la obiectul point
//din clasa derivata Point
arrayOfShapes[0] = &point;
//arrayOfShapes[1] pointeaza la obiectul circle
//din clasa derivata Circle
arrayOfShapes[1] = &circle;
//arrayOfShapes[2] pointeaza la obiectul cylinder
//din clasa derivata Cylinder
arrayOfShapes[2] = &cylinder;
//Se parcurge tabloul arrayOfShapes
//si se apeleaza virtualViaPointer
//pentru tiparirea numelui formei, atributelor,
//ariei si volumului fiecarui obiect prin legare dinamica
cout << "\nApeluri de functii virtuale prin "
<< "pointeri la clasa de baza\n";
for(int i = 0; i < 3; i++)
9

Programarea calculatoarelor i limbaje de programare II

virtualViaPointer(arrayOfShapes[i]);
//Se parcurge tabloul arrayOfShapes
//si se apeleaza virtualViaReference
//pentru tiparirea numelui formei, atributelor,
//ariei si volumului fiecarui obiect prin legare dinamica
cout << "Apeluri de functii virtuale prin "
<< "referinte la clasa de baza\n";
for(int i = 0; i < 3; i++)
virtualViaReference(*arrayOfShapes[i]);
return 0;
}
//Permite apelul functiilor virtuale printr-un pointer
//la clasa de baza folosind legarea dinamica
void virtualViaPointer(const Shape* baseClassPtr)
{
baseClassPtr->printShapeName();
baseClassPtr->print();
cout << "\nAria = " << baseClassPtr->area()
<< "\nVolumul = " << baseClassPtr->volume() << "\n\n";
}
//Permite apelul functiilor virtuale printr-o referinta
//la clasa de baza folosind legarea dinamica
void virtualViaReference(const Shape& baseClassRef)
{
baseClassRef.printShapeName();
baseClassRef.print();
cout << "\nAria = " << baseClassRef.area()
<< "\nVolumul = " << baseClassRef.volume() << "\n\n";
}
Rulnd acest program obinem urmtorul rezultat:
Point: [7, 11]
Circle: [22, 8]; Raza = 3.50
Cylinder: [10, 10]; Raza = 3.30; Inaltimea = 10.00
Apeluri de functii virtuale prin pointeri la clasa de baza
Point: [7, 11]
Aria = 0.00
Volumul = 0.00
Circle: [22, 8]; Raza = 3.50
Aria = 38.48
Volumul = 0.00
Cylinder: [10, 10]; Raza = 3.30; Inaltimea = 10.00
Aria = 275.77
Volumul = 342.12

10

Programarea calculatoarelor i limbaje de programare II

Apeluri de functii virtuale prin referinte la clasa de baza


Point: [7, 11]
Aria = 0.00
Volumul = 0.00
Circle: [22, 8]; Raza = 3.50
Aria = 38.48
Volumul = 0.00
Cylinder: [10, 10]; Raza = 3.30; Inaltimea = 10.00
Aria = 275.77
Volumul = 342.12
n programul de mai sus, arrayOfShapes este un tablou de pointeri la Shape.
Elementele sale sunt iniializate cu pointeri la Point, Circle i Cylinder care
sunt clase derivate din Shape. Ciclul for parcurge elementele tabloului care sunt
transmise pe rnd ca parametri funciei virtualViaPointer:
virtualViaPointer(arrayOfShapes[i]);
Paramentrul funciei este recepionat n pointerul baseClassPtr de tip const
Shape* i, prin intermediul su, se fac apelurile de funcii virtuale:
baseClassPtr->printShapeName()
baseClassPtr->print()
baseClassPtr->area()
baseClassPtr->volume()
Fiecare dintre aceste apeluri invoc funcia virtual a obiectului ctre care
baesClassPtr pointeaz n momentul apelului, obiect al crui tip nu poate fi
determinat la compilare.
Asemntor, funcia virtualViaReference este invocat pe rnd cu trei
parametri care reprezint referine la cele trei obiecte ale cror adrese sunt stocate n
arrayOfShapes:
virtualViaReference(*arrayOfShapes[i]);
Funcia virtualViaReference ncarc valoarea parametrului n baseClassRef
de tip const Shape& i apeleaz funciile virtuale astfel:
baseClassRef.printShapeName()
baseClassRef.print()
baseClassRef.area()
baseClassRef.volume()

11