Polimorfism. Clase abstracte.
Polimorfismul
• A treia trasatura esentiala in POO, dupa abstractizarea datelor si mostenire
• O alta dimensiune a separarii intre interfata si implementare, intre CE si CUM
• Determina o mai buna lizibilitate a codului, organizare mai eficienta si
dezvoltarea de programe extensibile
Polimorfism
Definitia 1: Polimorfismul este proprietatea unei entitati de a reactiona diferit in functie
de starea sa.
Definitia 2: Polimorfismul este proprietatea care permite unor entitati diferite sa se
comporte diferit la aceeasi actiune.
Definitia 3: Polimorfismul permite unor obiecte diferite sa raspunda diferit la acelasi
mesaj.
Figuri geometrice
Point.h Shape.h (2)
#ifndef POINT_H class Circle:public Shape{
#define POINT_H private:
#include <iostream> Point c;
using namespace std; float r;
class Point{ public:
int x,y; Circle(Point c1, float r1):c(c1),r(r1){}
void draw(){cout<<"Circle::draw()";}
public: float area(){cout<<"Circle::area()";
Point():x(0),y(0){} return PI*r*r;}
Point(int x, int y):x(x),y(y){} float perimeter()
Point(const Point& p):x(p.x),y(p.y{} {cout<<"Circle::perimeter()"; return
Point(int x):x(x),y(0){ 2*PI*r;}
} ~Circle(){}
int getX(){ return x; } };
int getY(){ return y; }
~Point(){ }
}; Shape
#endif
+draw(): void
+area(): float
+perimeter(): float
<<destroy>>+Shape()
Shape.h (1)
#ifndef SHAPE_H
#define SHAPE_H
#include "Point.h"
#include <cmath> Circle
#include <iostream> -r: float
<<create>>+Circle(c1: Point, r1: float)
using namespace std; +draw(): void
+area(): float
+perimeter(): float
const double PI=3.14; <<destroy>>+Circle()
class Shape{
public:
void draw()
{cout<<"Shape::draw()"<<endl;}
-c Point
float area()
{cout<<"Shape::area()"<<endl; return -x: int
0;} -y: int
float perimeter() <<create>>+Point()
{cout<<"Shape::perimeter()"<<endl; <<create>>+Point(x: int, y: int)
<<create>>+Point(p: Point)
return 0;} <<create>>+Point(x: int)
~Shape(){} +getX(): int
+getY(): int
}; <<destroy>>+Point()
Shape
Shape.h (3) +draw(): void
class Square:public Shape{ +area(): float
private: +perimeter(): float
<<destroy>>+Shape()
Point leftUp, rightDown;
public:
Square(Point p1, Point
p2):leftUp(p1), rightDown(p2){}
Square
void draw()
<<create>>+Square(p1: Point, p2: Point)
{cout<<"Square::draw()"<<endl;} +draw(): void
+area(): float
float area() +perimeter(): float
{cout<<"Square::area()"<<endl; <<destroy>>+Square()
float l= abs((float)(leftUp.getX()-
rightDown.getX()));
return l*l;}
float perimeter(){ -leftUp
cout<<"Square::perimeter()"<<endl; Point
return 4*abs((float)(leftUp.getX()- -rightDown
rightDown.getX()));} -x: int
-y: int
~Square(){} <<create>>+Point()
}; <<create>>+Point(x: int, y: int)
<<create>>+Point(p: Point)
#endif <<create>>+Point(x: int)
+getX(): int
+getY(): int
Test.cpp <<destroy>>+Point()
#include "Shape.h" 25π
void printArea(Shape& x){ 5
cout<<x.area()<<endl; 1
}
int main(){ 1
Shape* x1 = new Circle(Point(1,2),5);
Point p1(0,0), p2(1,1);
Shape* x2 = new Square(p1, p2);
printArea(*x1);
printArea(*x2);
return 0; Motivul?
• Legarea (binding) –conectarea
unui apel al unei functii la corpul
Realitatea…. functiei
• Legarea timpurie (early si adrese ale clasei de baza
binding) – stabilirea la momentul (referinte sau pointeri)
compilarii a metodelor care vor fi
apelate (compilatoare C) • La folosirea obiectelor transmise
prin valoare - object slicing la
• Solutia? upcasting
Object slicing
• Legarea intarziata (late binding, • Diferenta intre transmiterea prin
dynamic binding, runtime valoare/ transmiterea prin adresa
binding) – stabilirea doar la
momentul executiei a metodelor • Este recomandata transmiterea de
care vor fi apelate – functii adrese (au aceesi dimensiune)…
virtuale altfel, de obicei obiectele
derivate sunt “mai mari” decat
• Compilatorul nu va sti tipul obiectele clasei de baza
actual al obiectului, dar insereaza
cod care determina corpul corect • Cand se face upcast la un obiect
al functiei al clasei de baza – obiectul este
Functii virtuale “decupat” astfel incat ramane
• Declaratia doar partea comuna cu obiectele
virtual <antet_functie> clasei de baza
– Declaratia unei functii
virtuale se face o singura
data, in clasa de baza, ea
ramanand virtuala in
clasele derivate
–Daca o functie este
declarata virtuala in clasa
de baza si este suprascrisa
intr-o clasa derivata, are
loc legarea intarziata =
apelul metodei
corespunzatoare depinde
de tipul actual al
obiectului apelant
• CONSTRUCTORII
– NU pot fi virtuali (ei Persoana.h
instaleaza VPTR) #ifndef PERSOANA_H
#define PERSOANA_H
#include <cstring>
• DESTRUCTORII #include <cstdio>
– POT fi virtuali si in unele #include <iostream>
situatii chiar TREBUIE using namespace std;
sa fie virtuali
class Persoana{
protected:
• Legarea intarziata are loc numai char* nume;
cand se folosesc functii virtuale int varsta;
public: print(*p);
Persoana(char* n, int v){ delete(p);
nume=new char[strlen(n)+1]; system("pause");
strcpy(nume,n); return 0;
varsta=v;} Persoana(const };
Persoana& p){ #include "Persoana.h"
nume=new char[strlen(p.nume)+1]; #include <iostream>
strcpy(nume,p.nume); using namespace std;
varsta=p.varsta; }
virtual char* toString(){ void print (Persoana& p){
char* aux=new char[25] cout<<p.toString()<<endl;
sprintf(aux,"%s %d", nume,varsta); }
return aux;}
~Persoana(){ int main(){
if (nume) delete [] nume;} Persoana* p= new
}; Student("Ioana",21,"Informatica");
print(*p);
class Student:public Persoana{ delete(p);
char* facultate; return 0;
public: };
Student(char* n, int v, char*
f):Persoana(n,v){
facultate=new char[strlen(f)
+1]; …………………………………………
strcpy(facultate,f); Object slicing
} faculte?
…………………………………………
…………………………………………
Student(const Student& s):Persoana(s){ …………………………………………
facultate=new char[strlen(s.facultate)+1]; …………………………………………
strcpy(facultate,s.facultate); …………………………………………
Destructor virtual facultate???
}
#ifndef PERSOANA_H
char* toString(){
#define PERSOANA_H
char* aux = new char[30];
#include <cstring>
sprintf(aux,"%s %d %s",nume,
#include <cstdio>
varsta,facultate);
return aux;
class Persoana{
}
protected:
char* nume;
~Student(){
int varsta;
if (facultate)
delete [] facultate;
public:
}
Persoana(char* n, int v){
};
nume=new char[strlen(n)+1];
#endif strcpy(nume,n);
varsta=v;
Test.cpp }
#include "Persoana.h"
#include <iostream> virtual char* toString(){
Object slicing
using namespace std; char* aux=new char[25];
sprintf(aux,"%s %d", nume,
void print (Persoana p){ varsta);
cout<<p.toString()<<endl; return aux;
} }
int main(){ virtual ~Persoana(){
Persoana* p= new if (nume)
Student("Ioana",21,"Informatica"); delete [] nume;
}
};
Mecanismul
Obiect
VPTR
virtual_method1
virtual_method2
Virtual Method ...
Dispatch Table
(VTABLE)
• Fiecare clasa care are cel putin o
metoda virtuala are o data
membra ascunsa – pointer la {cout<<"Shape::draw()"<<endl;}
tabela functiilor virtuale – virtual float area()
{cout<<"Shape::area()"<<endl; return 0;}
contine adresele functiilor virtual float perimeter()
virtuale pentru clasa {cout<<"Shape::perimeter()"<<endl;return 0;}
corespunzatoare virtual ~Shape(){}
};
• Cand se face un apel folosind un
pointer sau o referinta la un astfel
de obiect, compilatorul genereaza class Circle:public Shape{
cod care dereferentiaza pointerul private:
spre tabela de functii virtuale a Point c;
clasei si face un apel indirect float r;
// const double PI=3.14;
folosind adresa functiei membre public:
a clasei din tabela functtilor Circle(Point c1, float r1):c(c1),r(r1){}
virtuale void draw(){cout<<"Circle::draw()";}
&Circle::draw float area(){cout<<"Circle::area()"; return
Circle &Circle::perimete PI*r*r;}
Shape* vptr r
&Circle::area float perimeter(){cout<<"Circle::perimeter()";
&Square::draw
return 2*PI*r;}
Square
vptr &Square::perimeter
~Circle(){}
};
&Square::area
Circle
vptr &Circle::draw class Square:public Shape{
&Circle::perimete
private:
r Point leftUp, rightDown;
&Circle::area
public:
• . Un obiect al unei clase derivate Square(Point p1, Point p2):leftUp(p1),
care a suprascris (override) rightDown(p2){}
implementarea unei metode void draw(){cout<<"Square::draw()"<<endl;}
virtuale din clasa de baza, are float area(){cout<<"Square::area()"<<endl;
float l= abs(leftUp.getX()-
pointerul VPTR indicand spre o rightDown.getX());
VTABLE diferita de a clasei de return l*l;}
baza, unde inregistrarea float perimeter()
corespunzatoare acelei metode {cout<<"Square::perimeter()"<<endl;
contine adresa functiei return 4*abs(leftUp.getX()-
rightDown.getX());
suprascrise }
~Square(){}
• Shape – metodele ar trebui sa fie };
virtuale… #endif
Test.cpp
Shape.h (versiunea 2) #include "Shape.h"
#ifndef SHAPE_H
#define SHAPE_H void printArea(Shape& x){
#include <cmath> cout<<x.area()<<endl;
#include <iostream> }
#include "Point.h"
using namespace std; int main(){
Shape* x1 = newCircle(Point(1,2),5);
const double PI=3.14; Point p1(0,0), p2(1,1);
class Shape{ Shape* x2 = new Square(p1, p2);
public: printArea(*x1);
virtual void draw() printArea(*x2);
return 0; – Oride reprezentare
} partajata (atribut comun)
25π – Orice functie membra
comuna (comportament
5 comun)
1
• O clasa abstracta nu are
instante!!!! (dar se pot defini
1 pointeri sau referinte la astfel de
clase)
• Are functii virtuale pure
Functii virtuale pure
• Declarare:
Shape.h
virtual <tip_returnat> <nume>(<lpf>)=0;
#ifndef SHAPE_H
#define SHAPE_H
#include "Point.h" •Mecanism:
#include <cmath> – Compilatorul rezerva un
#include <iostream> slot in VTABLE, dar nu
pune nici o adresa in acel
using namespace std;
slot – VTABLE nu va fi
const double PI=3.14; completa chiar daca
exista o singura functie
class Shape{
public:
“dummy code” virtuala pura
virtual void draw()
{cout<<"Shape::draw()"<<endl;} • Clasa abstracta pura – are numai
virtual float area() functii virtuale pure = interfata
{cout<<"Shape::area()"<<endl; return 0;} Clase concrete care mostenesc
virtual float perimeter() clase abstracte
{cout<<"Shape::perimeter()"<<endl; return 0;}
virtual ~Shape(){} • O clasa concreta care mosteneste
}; interfata publica a clasei
abstracte
• O clasa concreta se doreste sa
aiba instante
• Suprascrie metodele abstracte
(pure) pentru a furniza
implementari concrete specific
Clase abstracte reprezentarii proprii, altfel devine
• O clasa abstracta foloseste ca la randul sau o clasa abstracta
baza pentru o colectie de clase
derivate - prezinta o interfata • Shape – interfata – descrie
pentru clasele derivate comportamentul comun tuturor
figurilor geometrice
• Ea furnizeaza: Shape.h (versiunea 3)
– O interfata comuna #ifndef SHAPE_H
publica (functii virtuale #define SHAPE_H
#include <cmath>
pure)
#include <iostream>
#include "Point.h"
using namespace std; Shape
const double PI=3.14;
class Shape{ +draw(): void
public: +area(): float
virtual void draw()=0; +perimeter(): float
virtual float area()=0; <<destroy>>+Shape()
virtual float perimeter()=0;
virtual ~Shape()=0;
};
Avantajele polimorfimsului
Shape::~Shape(){} • Functii polimorfice
void printArea(Shape& s){
cout<<s.area()<<“ “;
Destructor virtual pur – are }
nevoie intotdeauna de corp vid
• Structuri de date polimorfice
– containere cu diferite
tipuri de elemente (fara
void* !!!)
Vector cu elemente generice
Test.cpp Array
#include "Shape.h" <<CppSynonym>> TElem
<<CppTypedef>> -elem
-n: int
int main(){ PElem
+equals(: PElem): int
Shape* s[3]; +toString(): char
<<create>>+Array(n1: int)
Point center(2,3); +add(p: PElem): void
<<destroy>>+TElem() +getElem(pos: int): PElem
s[0]=new Circle(center,4); +setElem(p: PElem, pos: int): void
Point lU(2,2), rD(5,5); +length(): int
s[1]= new Square(lU,rD); +remove(pos: int): void
<<destroy>>+Array()
s[2]=new Circle(lU,7);
for(int i=0;i<3;i++)
cout<<s[i]->area()<<endl; Integer Person
for(int i=0;i<3;i++) -i: int -name: char
delete s[i]; <<create>>+Integer(n: int) <<create>>+Person(n: char)
+toString(): char +toString(): char
+equals(p: PElem): int +equals(p: PElem): int
return 0; <<destroy>>+Integer() <<destroy>>+Person()
}
s Element.h
#ifndef ELEM_H
#define ELEM_H
class TElem;
typedef TElem* PElem;
class TElem{
public:
virtual int equals(PElem)=0;
Reprezentarea UML – clase virtual char* toString()=0;
abstracte virtual ~TElem(){};
};
#endif #ifndef PERSON_H
#define PERSON_H
<<CppSynonym>> TElem #include "Element.h"
<<CppTypedef>>
PElem
+equals(: PElem): int class Person:public TElem{
+toString(): char
<<destroy>>+TElem()
char* name;
public:
Person(char* n){
Integer.h name=new char[strlen(n)+1];
#ifndef INTEGER_H strcpy(name,n);
#define INTEGER_H }
#include <cstdio>
#include <cstring>
#include "Element.h" char* toString(){return name;}
using namespace std;
int equals(PElem p){
class Integer:public TElem{ return strcmp(name, ((Person*)p)-
private:
int i; >name)==0;
public: }
Integer(int n=0):i(n){}
~Person(){ delete [] name;}
char* toString(){ };
char* buf=new char[3];
sprintf(buf,"%d",i);
return buf;} #endif
int equals(PElem p){ return i==((Integer*)p)-
>i;} TElem
~Integer(){}
}; +equals(: PElem): int
#endif +toString(): char
<<destroy>>+TElem()
<<CppSynonym>> TElem
<<CppTypedef>>
PElem
+equals(: PElem): int
+toString(): char Person
<<destroy>>+TElem()
-name: char
<<create>>+Person(n: char)
+toString(): char
+equals(p: PElem): int
<<destroy>>+Person()
Integer
-i: int
<<create>>+Integer(n: int)
+toString(): char
+equals(p: PElem): int
<<destroy>>+Integer()
Array.h
#ifndef ARRAY_H
#define ARRAY_H
Person.h
#include "Element.h"
class Array{
PElem* elem;
int n;
public:
Array(int n1){elem = new PElem[n1]; n=0;}
void add(PElem p){elem[n++]=p;}
PElem getElem(int pos){return elem[pos];}
void setElem(PElem p, int pos){elem[pos]=p;}
int length(){return n;}
void remove(int pos){elem[pos]=elem[--n];}
~Array(){for(int i=0;i<n;i++)
delete elem[i];
delete [] elem;}
};
#endif
Array
<<CppSynonym>> TElem
<<CppTypedef>> -elem
-n: int
PElem
+equals(: PElem): int <<create>>+Array(n1: int)
+toString(): char +add(p: PElem): void
<<destroy>>+TElem() Diamond inheritance
+getElem(pos: int): PElem
+setElem(p: PElem, pos: int): void
TestArray.cpp class Right:public Top{
+length(): int
protected:
class Left:public Top{
#include "Integer.h" +remove(pos: int): voidint z; protected: int y;
public: Right(int x1, int
<<destroy>>+Array() public: Left (int x1, int
#include "Person.h" z1):Top(x1){z=z1;} y1):Top(x1){y=y1;}
#include "Array.h" }; };
#include <iostream> #ifndef DI_H
using namespace std; #define DI_H
…
void print(Array& a){ class Top{
protected: int x;
for(int i=0;i<a.length();i++) public:Top(int x1){x=x1;}
cout<<a.getElem(i)->toString()<<" "; };
cout<<endl;
class Bottom: public Left, public Right{
}
int w;
public:
int main(){
Array a(4);
Bottom(int x1, int y1, int z1, int w1):
a.add(new Integer(3));
/*Top(x1),*/Left(x1, y1), Right(x1, z1)
a.add(new Integer(4));
{w=w1;}//Top is not a base class of Bottom
a.add(new Integer(5));
a.add(new Person("Adriana"));
friend ostream& operator<<(ostream& os,
print(a);
Bottom& b){
return 0;
return os<<" "<<b.y<<" "<<b.z<<"
}
"<<b.w<<endl;//x …ambiguous
};
Mostenire multipla #endif
Diamond inheritance
Diamond inheritance
#include "DiamondInheritance.h"
int main(){ int y;
public:
Left (int x1, int y1):Top(x1){y=y1;}
Bottom b(1,2,3,4); };
cout<<sizeof(b)<<endl; class Right: virtual public Top{
cout<<b; protected:
return 0; int z;
} public:
Right(int x1, int z1):Top(x1){z=z1;}
};
class Bottom: public Left, public Right{
int w;
public:
True diamond inheritance //ultima dintre clasele derivate va trebui sa
initializeze clasa de baza, altfel vor exista
ambiguitati (Left sau Right?)
Bottom(int x1, int y1, int z1, int w1): Top(x1),
Left(0, y1), Right(0, z1){w=w1;}
friend ostream& operator<<(ostream& os,
Bottom& b){
return os<<b.x<<" "<<b.y<<" "<<b.z<<"
"<<b.w<<endl;//x poate fi tiparit...virtual
}
};
#endif
True diamond inheritance
#include "DiamondInheritance.h"
int main(){
Bottom b(1,2,3,4);
cout<<sizeof(b)<<endl;
cout<<b;
return 0;
}
Mostenire virtuala
#ifndef DI_H
#define DI_H True diamond inheritance
#include <iostream>
using namespace std;
• Cand b:Bottom este instantiat,
obiectul b va aparea astfel:
class Top{
protected:
int x; Left
public:
Top(int x1){x=x1;} Right
virtual ~Top(){}//all base classes must have
virtual destructors
};
Bottom
class Left: virtual public Top{ Top
protected:
• Subobiectele Left si Right are
fiecare cate un pointer la acelasi
subobiect partajat Top
• Cand apare mostenirea multipla,
un obiect al clasei derivate se
comporta ca si cum ar avea mai
multi VPTR, cate unul pentru
fiecare clasa de baza directa
Concluzii
• Mostenirea multipla –
nerecomandata –
– solutia Java –
mostenire simpla si
implementare de
interfete multiple
• Mecanismul functiilor
virtuale – de ce nu implicit?
– motive de eficienta